首页 > 其他分享 >120.strcpy函数和strncpy函数的区别?哪个函数更安全?

120.strcpy函数和strncpy函数的区别?哪个函数更安全?

时间:2023-08-02 21:33:59浏览次数:38  
标签:char 函数 src 烫烫 dst len 120 strcpy strncpy

120.strcpy函数和strncpy函数的区别?哪个函数更安全?

1.函数原型

char* strcpy(char* strDest, const char* strSrc)
char *strncpy(char *dest, const char *src, size_t n)

2.安全性

  • strcpy函数: 如果参数 dest 所指的内存空间不够大,可能会造成缓冲溢出(buffer Overflow)的错误情况,在编写程序时请特别留意,或者用strncpy()来取代。
  • strncpy函数:用来复制源字符串的前n个字符,src 和 dest 所指的内存区域不能重叠,且 dest 必须有足够的空间放置n个字符。

trncpystrcpy更安全的原因如下:

  1. 目标字符串大小限制:strncpy函数接受一个目标字符串的大小限制参数,这可以确保复制的字符数不会超过目标字符串的容量。因此,即使源字符串的长度超过了目标字符串的大小,也只会发生部分复制,从而避免了缓冲区溢出的问题。
  2. 源字符串长度不确定:在某些情况下,源字符串的长度是未知的,或者无法通过其他方式确定。使用strncpy函数可以避免在不知道源字符串长度的情况下使用strcpy导致的潜在风险。
  3. 内存安全性:由于strncpy可以指定复制的字符数,因此可以避免在目标字符串中复制不必要的字符,从而避免了潜在的内存越界问题。

1.字符串函数strcpy

(1)strcpy函数

char* strcpy(char* destination, const char * source);

strcpy是覆盖拷贝,将source全覆盖拷贝到destination,会把’\0’也拷过去,且必须考虑destination的空间够不够
(destination的空间必须>=source的空间)

(2)strcpy的使用

a.源空间小于等于目标空间,正确

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
	char p1[] = "abcdef";
	char* p2 = "hello";
	strcpy(p1, p2);
	printf("%s\n", p1);
	printf("%s\n", p2);
	return 0;
}

输出:

hello
hello

b.源空间大于目标空间,错误

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
	char p1[] = "abcd11";
	char* p2 = "hello";
	strcpy(p1, p2);
	printf("%s\n", p1);
	printf("%s\n", p2);
	return 0;
}

c.注意常量不能被赋值

代码:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
	char p1[] = "abcdef";
	char* p2 = "hello";
	const char* p3 = "world";
	strcpy(p1, p3);//正确
	strcpy(p3, p1);//错误
	//strcpy(p2, p3);
	printf("%s\n", p1);
	printf("%s\n", p3);
	return 0;
}

错误分析:

(3)模拟实现strcpy

a.代码

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
void MyStrcpy(char* dst, const char* src)
{
	while (*src)
	{
		*dst = *src;
		++src;
		++dst;
	}
	*dst = '\0';
}
int main()
{
	char p1[] = "abcdef";
	const char* p2 = "hello";
	MyStrcpy(p1, p2);
	printf("%s\n", p1);
	printf("%s\n", p2);
	return 0;
}

b.运行结果

hello
hello

2.strncpy

2.1strncpy库函数

char *strncpy(char *dest, const char *src, size_t len);

strncpy函数含有三个参数:

第一个参数是指向目标空间的起始地址 char* dest

第二个参数是源头字符串的起始地址 char* src

第三个参数是一个无符号整型size_t len写入的字符数

返回类型为 char* 即返回目标空间字符串的起始地址。

该函数的作用为:

strncpy字符串将源头字符串的内容复制到目标空间中,但是并不全部复制,而是写入的字符数目由len指定,即向目标空间拷贝len个字符。

如果源头字符串的数目小于len,那么目标空间中将会用额外的 '\0' 填充到len个数目长度,如果源头字符串的数目大于或等于len,那么将只有len个字符拷贝到目标空间,结果将不会以'\0' 结尾
————————————————
使用情形1:

1)src串长度<=dest串长度,(这里的串长度包含串尾NULL字符)

①len>=src串长度,正确(len>dest串长度也正确)

则将src串全部拷贝指定长len,超过src串长度的部分到指定长len部分,自动加上'/0',剩余的保持原状态

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>

int main()
{
	char str[] = { '1', '2', '3', '4', '5', '6', '7', '8' };
	char* src = "abcd";

	strncpy(str, src, 5);

	printf("%s\n", str);
        //printf("%s\n", str[6]);//err不可访问
	system("pause");
	return 0;
}

输出:

abcd

②指定长len<src串长度,则将源长中按指定长度拷贝到目标字符串,不包括'/0'

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>

int main()
{
	char str[] = { '1', '2', '3', '4', '5', '6', '7', '8' };
	char* src = "abcd";

	strncpy(str, src, 2);

	printf("%s\n", str);

	system("pause");
	return 0;
}

输出:

ab345678烫烫烫烫烫烫烫烫烫烫烫烫$溹蓣

手动加'\0'

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>

int main()
{
	char str[] = { '1', '2', '3', '4', '5', '6', '7', '8' };
	char* src = "abcd";
	//char src[] = "abcd";
	strncpy(str, src, 2);
	str[2] = '\0';

	printf("%s\n", str);

	system("pause");
	return 0;
}

输出:

ab

2)src串长度>dest串长度

①指定长len>=src串长度,错误,溢出,但可能有输出

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>

int main()
{
	char str[] = { '1', '2', '3' };
	char* src = "abcd";

	strncpy(str, src, 5);

	printf("%s\n", str);

	system("pause");
	return 0;
}

输出:

abcd

②指定长len<src串长度,错误

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>

int main()
{
	char str[] = { '1', '2', '3' };
	char* src = "abcd";

	strncpy(str, src, 3);

	printf("%s\n", str);

	system("pause");
	return 0;
}

输出:

abc烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫?溙洒

2.2函数实现

2.2.1不考虑内存重叠的strncpy

char *strncpy(char *dst, const char *src, size_t len)
{
	assert(dst != NULL && src != NULL);
	char *res = dst;
	while (len--)
	{
		*dst++ = *src++;
	}
	return res;
}

看着好像没啥问题,但是,当src的长度小于len呢?这份代码没有处理这个问题。当src的长度小于len时,应该如何处理?《C和指针》p179给出的答案是:

“ 和strcpy一样,strncpy把源字符串的字符复制到目标数组。然而,它总是正好向dst写入len个字符。如果strlen(src)的值小于len,dst数组就用额外的NULL字节填充到len长度,如果strlen(src)的值大于或等于len,那么只有len个字符被复制到dst中。”

注意!它的结果将不会以NUL字节结尾。(NUL即‘\0’)。

由此可见,我们还需要判断strlen(src)是否小于len,如果是,还需要在dst后面添加NUL,因此,正确的代码应该如下:

char *strncpy(char *dest, const char *src, size_t len)
{
	assert(dest != NULL && src != NULL);
    
	char *res = dest;
	int offset = 0;
    
	if (strlen(src) < len)//src长度小于len
	{
		offset = len - strlen(src);
		len = strlen(src);
	}

	while (len--)
	{
		*dest++ = *src++;
	}
	while (offset--)
	{
		*dest++ = '\0';
	}
	return res;
}

使用这个函数,尤其需要注意,不要出现len>strlen(dst)的情况,如果len>strlen(dst),那么会破坏dst后面的内存:

我们假设前面红色部分是dst,然后strncpy(dst,src,10);那么后面黄色部分的内存就被破坏了。strncpy是不负责检测len是否大于dst长度的。

总的来说,strncpy总是复制len个字符到dst指向的内存!!!

所以,还会出现下面的情况

char message[] = "abcd";
strncpy(message, "abcde",5);
cout << message;

输出是abcde烫烫烫烫烫烫烫烫烫烫烫烫烫烫 (结果不唯一)

message的内存是有5个字节的,但是将abcde拷贝过去时,最后面的‘\0’被覆盖了,strncpy并不会负责添加‘\0’到dst结尾,因此,输出该字符串是,会在e字符后面一直找到‘\0’才结束,因此就会出现乱码。

2.2.2考虑内存重叠的strncpy

面试中经常会遇到让你写一个能够处理内存重叠的strncpy,标准库中的strncpy是不考虑内存重叠的,如果出现内存重叠,结果将是未定义的。
网上的很多博客也有这个代码的实现,其实很多也是有问题的,没有考虑src长度小于len的问题:

char *strncpy(char *dst, const char *src, size_t len)
{
	assert(dst != NULL && src != NULL);
	char *res = dst;
	if (dst >= src && dst <= src + len - 1)//重叠,从后向前复制
	{
		dst = dst + len - 1;
		src = src + len - 1;
		while (len--)
			*dst-- = *src--;
	}
	else
	{
		while (len--)
			*dst++ = *src++;
	}
	return res;
}

那么,如果要处理内存重叠,该怎么办?如果内存重叠和src的长度小于len这两种情况同时出现,又如何处理?

见图,假设红色部分为src,黄色为dst。如果出现内存重叠,我们很容易想到:从后往前拷贝。如果src的长度小于len,则在后面补NULL。

char *strncpy(char *dst, const char *src, size_t len)
{
	assert(dst != NULL && src != NULL);
	char *res = dst;
	int offset = 0;
	char *tmp;
	if (strlen(src) < len)//src长度小于len
	{
		offset = len - strlen(src);
		len = strlen(src);
	}

	if (dst >= src && dst <= src + len - 1)//重叠,从后向前复制
	{
		dst = dst + len - 1;
		src = src + len - 1;
		tmp = dst;
		while (len--)
			*dst-- = *src--;
	}
	else
	{
		while (len--)
			*dst++ = *src++;
		tmp = dst;
	}
	while (offset--)
	{
		*tmp++ = '\0';
	}
	return res;
}

那么,如果len的值大于dst的值,就会破坏dst后面的内存空间,这应该是要避免的。

最后,我们看一个有意思的东西:(此处strncpy是考虑内存重叠的版本)

message的长度增加了0.0 当然 ,它后面的内存被破坏了,这可能带来严重的后果。
最后,使用strncpy时,最好自动添加‘\0’在结尾。

参考:[(7条消息) 【C语言】strncpy详解_信手斩龙的博客-CSDN博客]

标签:char,函数,src,烫烫,dst,len,120,strcpy,strncpy
From: https://www.cnblogs.com/codemagiciant/p/17601796.html

相关文章

  • [8月摸鱼计划]无法将“ssh”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
    无法将“ssh”项识别为cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次gitee生成自己的公钥之后,运行ssh-T [email protected]检测是否成功却说没办法识别ssh遇到了类似的问题在powershell会这样。所以我直接到gitbash里面去敲......
  • fluent:壁面函数/边界层/y+
    速度边界层根据速度边界层理论:具有黏性的流体,经过壁面附近流速下降。所以在壁面处流体速度可以认为u=0,随着离壁面越来越远,流体速度也会增加。为什么要用壁面函数为了不划分更细的网格也可以捕捉到边界层速度,引入了壁面函数的说法,也就是y+。定义分母是运动粘度,其中y表示距离......
  • 字符串转化为整数的C库函数
    #include<stdio.h>#include<stdlib.h>intmain(void){charstr[10]="12345";charstr1[10]="hello";intval;val=atoi(str);printf("val=%d,str=%s\r\n",val,str);val=atoi(s......
  • 进程注入检测 —— RtlCaptureStackBackTrace 获取当前函数的调用栈函数
    https://stackoverflow.com/questions/590160/how-to-log-stack-frames-with-windows-x64 https://cpp.hotexamples.com/examples/-/-/RtlCaptureStackBackTrace/cpp-rtlcapturestackbacktrace-function-examples.html  例子参考  平日里用VS开发工具在调时在Debug下有一个选......
  • 实验七 字符串的内建函数
    实验七字符串的内建函数一、实验目的1、培养分析问题并对进行建模的能力。2、熟练运用字符串内键函数解决实际问题。二、实验内容1、将字母全部转换为大写或小写,如:”ILovePython”转化结果:“ilovepython”或者“ILOVEPYTHON”2、判断用户名是否合法,从键盘上输入一个用户......
  • 实验十一 函数基本应用
    实验十一函数基本应用一、实验目的1、培养分析问题并对进行建模的能力。2、熟练运用函数解决实际问题。二、实验内容1、定义一个getMax函数,返回三个数(从键盘输入的整数)中的最大值。比如:输入:123返回:32、编写函数,求出"+1/(1*2)-1/(2*3)+1/(3*4)-1/4*5+…)"前n项的和,函......
  • 无涯教程-Lua - if语句函数
    if语句由布尔表达式组成,后跟一个或多个语句。ifstatement-语法Lua编程语言中的if语句的语法是-if(boolean_expression)then--[statement(s)willexecuteifthebooleanexpressionistrue--]end如果布尔表达式的输出为true,则将执行if语句中的代码块。如果......
  • 当编译器没有SetProcessDpiAwareness()这个函数时...
    #include<Shlobj.h>intsetdpi(){//定义一个函数指针类型typedefHRESULT(WINAPI*SetProcessDpiAwarenessFunc)(intvalue);//加载Shcore.dllHMODULEhModule=LoadLibrary("Shcore.dll");if(hModule==NULL){//加载失败......
  • 无涯教程-Lua - nested语句函数
    Lua编程语言允许在另一个循环中使用一个循环。以下部分显示了一些示例来说明这一概念。nestedloops-语法Lua中嵌套for循环语句的语法如下-forinit,max/minvalue,incrementdoforinit,max/minvalue,incrementdostatement(s)endstatement(s)en......
  • 无涯教程-Lua - repeat...until 语句函数
    与for和while循环(它们在循环顶部测试循环条件)不同,Lua编程中的repeat...until循环语言在循环的底部检查其条件。repeat...until循环与while循环相似,不同之处在于,保证do...while循环至少执行一次。repeat...untilloop-语法Lua编程语言中repeat...until循......