容易造成内存溢出的函数

scorlw 发布于

容易造成内存溢出的函数

c++

1、strcpy

是一种C语言的标准库函数,strcpy把含有’\0’结束符的字符串复制到另一个地址空间,返回值的类型为char*。

头文件:#include <string.h> 和 #include <stdio.h>

原型声明:char *strcpy(char* dest, const char *src);

功能:把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间

说明:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。返回指向dest的指针。

源码:

1
2
3
4
5
6
7
char* strcpy(char* des,const char* source)
{
char* r=des;
assert((des != NULL) && (source != NULL));
while((*r++ = *source++)!='\0');
return des;
}

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
char a[10],b[]={"COPY"};
//定义字符数组a,b
strcpy(a,b);
//将b中的COPY复制到a中

//or
void strcpytst()
{
char str[4] = {'6','2','3'};//默认第四个是'\0',如果{'6','2','3','4'}就没有'\0',此时strcpy函数运行到遇到'\0'为止
cout << &str << endl;//0x6dfedc
int a[100] = {0};//防止str与str1有重复内存

char str2[4] = {'a','s','d','f'};
cout << &str2 << endl;//0x6dfd48
char str1[1];
cout << &str1 << endl;//0x6dfd47
strcpy(str1, str);
cout << sizeof(str) <<endl;//4
cout << sizeof(str1) <<endl;//1
printf("%s and %s\n", str, str1);//623 and 623
cout << str1[0] << " " << str1[1] << " " << str1[2] << " " << str1[3] << " " << str1[4] << " "<< endl;//6 2 3 f
cout << str2[0] << " " << str2[1] << " " << str2[2] << " " << str2[3] << " " << str2[4] << " "<< endl;//2 3 f
}

解决方法:

strncpy

strncpy函数用于将指定长度的字符串复制到字符数组中。

头文件:#include <string.h> 和 #include <stdio.h>

原型声明:char *strncpy(char *destinin, char *source, int maxlen);

功能:把source指向的字符串的前size_t n个字符(不包括\0,\0得自己手动加在*destin被复制之后)复制到destin指向的字符串中。如果要复制的*source的部分有\0,就把\0复制进去,之后就提前结束,即使没复制到第size_t n个字符也是。返回指向*destin的指针。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void strncpytst()
{
char name[]={"Chinanet"},destin[20]={};
strncpy(destin,name,3);
printf("%s\n",destin);//Chi
strncpy(destin,name,8);
printf("%s\n",destin);//Chinanet
strncpy(destin,name,9);
printf("%s\n",destin);//Chinanet
char destin1[8] = {};
strncpy(destin1,name,9);
printf("%s\n",destin1);//Chinanet
char destin4[9] = {'4','5','6','7','8','9','0','p','q'};
char destin2[2] = {};
char destin3[4] = {'1','2','3'};
strncpy(destin2,name,9);
printf("%s\n",destin2);//Chinanet
cout << destin3[0] << " " << destin3[1] << " " << destin3[2] << " " << destin3[3] << " " << destin3[4] << " "<< endl;//1 2 3 C//destin4第四位默认为'\0'
cout << destin4[0] << " " << destin4[1] << " " << destin4[2] << " " << destin4[3] << " " << destin4[4] << " " << destin4[5] << " " << destin4[6] << " " << destin4[7] << " " << destin4[8] << " " << destin4[9] << " " << endl;//i n a n e t p q C//name字符串后默认补'\0'
char name1[]={'C','h','i','n','a','n','e','t'},destin6[9] = {'1','2','3','4','5','6','7','8','9'}, destin5[5]={};
strncpy(destin5,name1,8);
cout << destin6[0] << " " << destin6[1] << " " << destin6[2] << " " << destin6[3] << " " << destin6[4] << " " << destin6[5] << " " << destin6[6] << " " << destin6[7] << " " << destin6[8] << " " << destin6[9] << " " << endl;//n e t 4 5 6 7 8 9 C//strncpy不会自动添加'\0',最后的C是name1的
}

参考:https://baike.baidu.com/item/strcpy/5494519?fr=aladdin

2、strcat

是一种C语言的标准库函数,将两个char类型连接,返回值的类型为char*。

头文件:#include <string.h> 和 #include <stdio.h>

原型声明:extern char *strcat(char *dest, const char *src);

功能:把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除*dest原来末尾的“\0”)。要保证*dest足够长,以容纳被复制进来的*src。*src中原有的字符不变。返回指向dest的指针。

说明:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。返回指向dest的指针。

代码实现:

1
2
3
4
5
6
7
8
char *mystrcat(char *dst,const char *src) //用自己的方式实现strcat函数功能
{
char *p=dst; //下面的操作会改变目的指针指向,先定义一个指针记录dst
while(*p!='\0')p++;
while(*src != '\0')*p++=*src++;
*p='\0';
return dst;
}

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void strcattst()
{
char d[20] = "GoldenGlobal";
char* s = "View";
strcat(d,s);
printf("%s\n",d);//GoldenGlobalView

char d2[5] = "1234";
char d1[4] = "123";
char* s1 = "456";
strcat(d1,s1);
printf("%s\n",d1);//123456
printf("%s\n",d2);//56//strcat自动加'\0'
}

解决方法:

strncat

strncat()主要功能是在字符串的结尾追加n个字符。

头文件:#include <string.h> 和 #include <stdio.h>

原型声明:char *strncat(char *dest,char *src,int n);

功能:把src所指字符串的前n个字符添加到dest所指字符串的结尾处,并覆盖dest所指字符串结尾的’\0’,从而实现字符串的连接。strncat()会将dest字符串最后的’\0’覆盖掉,字符追加完成后,再追加’\0’。

举例:

1
2
3
4
5
6
7
void strncattst()
{
char dest[20] = "Hello";
char src[10] = "World";
strncat(dest, src, 5);
printf("%s\n", dest);//HelloWorld
}

参考:https://baike.baidu.com/item/strncat

https://baike.baidu.com/item/strcat

3、gets

gets从标准输入设备读字符串函数,其可以无限读取,不会判断上限,以回车结束读取,所以程序员应该确保buffer的空间足够大,以便在执行读操作时不发生溢出。

头文件:#include <stdio.h>

原型声明:char * gets (char * str);

功能:从stdin流中读取字符串,直至接受到换行符或EOF时停止,并将读取的结果存放在buffer指针所指向的字符数组中。换行符不作为读取串的内容,读取的换行符被转换为‘\0’空字符,并由此来结束字符串。

说明:由于可以无限读取,所以在2011年12月,ANSI 采纳了 ISO/IEC 9899:2011 标准,标准中删除了 gets()函数,使用一个新的更安全的函数gets_s()替代(具体用法看下面示例)。

举例:

1
2
3
4
5
6
7
8
char s[5];  //不要char*p,然后gets(p),这是错误的,因为p没有指向有效的内存,它可能指向任何非法地址地方的未知大小的内存块,这样以来,就可能修改了不属于本程序的内存的内容
gets(s);//123
printf("%s\n", s);//123

char str0[4];
char str[3];
gets(str);
printf("%s and %s\n", str0, str);//456789 and 123456789

gets(s)函数与scanf(“%s”,s)相似,但不完全相同,使用scanf(“%s”,s) 函数输入字符串时存在一个问题,就是如果输入了空格会认为字符串结束,空格后的字符将作为下一个输入项处理,但gets()函数将接收输入的整个字符串直到遇到换行为止。

gets()函数读取到\n(我们输入的回车)于是停止读取,但是它不会把\n包含到字符串里面去。然而,和它配合使用的puts函数,却在输出字符串的时候自动换行。

解决方法:

fgets

fgets函数功能为从指定的流中读取数据,每次读取一行。

头文件:#include <string.h> 和 #include <stdio.h>

原型声明:char *fgets(char *str, int n, FILE *stream);

功能:从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

举例:

1
2
3
4
char str1[9];
char str2[3];
fgets(str2,5,stdin);//123456789 //5包括'\0'
printf("%s and %s", str1, str2);//4 and 1234

参考:https://baike.baidu.com/item/gets

https://baike.baidu.com/item/fgets

4、sprintf

把格式化的数据写入某个字符串中,即发送格式化输出到 string 所指向的字符串。

头文件:#include <stdio.h>

原型声明:int sprintf(char *string, char *format [,argument,...]);

功能:主要功能是把格式化的数据写入某个字符串中,即发送格式化输出到 string 所指向的字符串。sprintf 是个变参函数。使用sprintf 对于写入buffer的字符数是没有限制的,这就存在了buffer溢出的可能性。

返回值

如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。

sprintf 返回以format为格式argument为内容组成的结果被写入string的字节数,结束字符‘\0’不计入内。即,如果“Hello”被写入空间足够大的string后,函数sprintf 返回5。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void sprintftst()
{
char buffer[200], s[] = "computer", c = 'l';
int i = 35, j = 0;
float fp = 1.7320534f;
// 格式化并打印各种数据到buffer
j = sprintf( buffer, " String: %s\n", s );
printf( "j = %d\n",j);//23
j += sprintf( buffer + j, " Character: %c\n", c );
printf( "j = %d\n",j);//39
j += sprintf( buffer + j, " Integer: %d\n", i );
printf( "j = %d\n",j);//56
j += sprintf( buffer + j, " Real: %f\n", fp );
printf( "j = %d\n",j);//79

printf( "Output:\n%s\ncharacter count = %d\n", buffer, j );
//Output:
//String: computer
//Character: l
//Integer: 35
//Real: 1.732053

//character count = 79
}

format标签属性

format 标签属性是%[flags][width][.precision][length]specifier,具体讲解如下:

sprintf格式的规格如下所示。[]中的部分是可选的。

%[指定参数][标识符][宽度][.精度]指示符

\1. [指定参数] 处理字符方向。负号时表示从后向前处理。

\2. [标识符] 填空字元。 0 的话表示空格填 0;空格是内定值,表示空格就放着。

\3. [宽度]字符总宽度。为最小宽度。

\4. [精度] 精确度。指在小数点后的浮点数位数。

5, 转换字符

%% 印出百分比符号,不转换。

%c 字符输出到缓冲区,不转换。

%d 整数转成十进位。

%f 倍精确度数字转成浮点数。

%o 整数转成八进位。

%s 字符串输出到缓冲区,不转换。

%x 整数转成小写十六进位。

%X 整数转成大写十六进位。

例如:

1
2
3
4
5
money = 123.1
formatted = sprintf ("%06.2f", $money); // 此时变数 $ formatted 值为 "123.10"
formatted = sprintf ("%08.2f", $money); // 此时变数 $ formatted 值为 "00123.10"
formatted = sprintf ("%-08.2f", $money); // 此时变数 $ formatted 值为 "123.1000"
formatted = sprintf ("%.2f%%", 0.95 * 100); // 格式化为百分比

解决方法:

sprintf_s和snprintf

sprintf_s()是sprintf()的安全版本,通过指定缓冲区长度来避免sprintf()存在的溢出风险 。在使用VS2008时如果你使用了sprintf函数,那么编译器会发出警告:使用sprintf存在风险,建议使用sprintf_s。这个安全版本的原型是:

int sprintf_s(char *buffer,size_t sizeOfBuffer,const char *format [,argument] ... );

不过sprintf_s()是微软私有的函数,考虑到跨平台移植,最好使用snprintf()。两者的原型基本相同:

int snprintf(char *buffer, size_t count, const char *format [,argument] ... );

参考:https://baike.baidu.com/item/sprintf