C语言没有提供字符串类型,字符串以字符数组的形式出现,C标准库提供了一些操作字符串的函数,主要有:strcmp 字符串比较函数,strcpy 字符串拷贝函数, strlen 字符串测长函数, strcat字符串连接函数,sprintf格式化字符串拷贝函数等等。因为字符串就是以‘\0’结束的一段内存,这些函数实质上也就是操作内存的函数,所以避免不了的与指针打交道,使得这些函数充满了陷阱,如果这些函数使用不当,很有可能在程序中埋伏下危险的陷阱,使程序的稳定性遭受重创。下面我就字符串使用中一些常见的问题来进行举例说明。
一. strcpy:极度危险的函数,一不小心就会中招,危险指数:四星
strcpy的原型是这样的: char *strcpy(char *dest, const char *src) 作为常见的字符串复制函数,C库中的实现是不安全的,因为它不做字符串的检查,以至于如果参数传入了非法指针,比如:src不是指向字符串的指针。后果就不堪设想,程序会一直复制,直到遇到‘\0’才结束,这样很有可能就会使得dest指向的内存区域缓冲区溢出,使得导致与程序不相干的部分出现错误,这种错误也许就是致命的, 所以使用这个函数一定确保第二个参数传入合法的指针。例子:
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- char dest[5] = {'D'};
- char mydata[7] = {'m','y','d','a','t','a','\0'};
- int main(void)
- {
- char i;
- char source[5];
- char bound[5] = {'&','&','&','&','&'};
- for (i = 0; i < 5; i++)
- source[i] = 'S';
- printf("before strcopy, mydata is %s\n", mydata);
- strcpy(dest, source);
- printf("dest is %s\n", dest);
- printf("after strcopy, mydata is %s\n", mydata);
- }
二. strcat 造成缓冲区溢出的隐形杀手,危险指数 三星
strcat 是将一个字符串连接到另外一个字符串上,其函数原型为char *strcat(char *dest,char *src)。这个函数也很危险,因为C语言的实现也是不安全的,传入非法的指针有可能会造成程序的崩溃。首先保证两个指针都应该指向字符串,其次dest指针指向的空间要足以容的下src指向的字符串,否则会造成缓冲区溢出而破坏其他程序数据。
例子1:
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- char dest[5] = {'D', '\0'};
- char mydata[7] = {'m','y','d','a','t','a','\0'};
- int main(void)
- {
- char i;
- char source[5];
- char bound[5] = {'&','&','&','&','&'};
- for (i = 0; i < 4; i++)
- source[i] = 'S';
- source[4] = '\0';
- printf("before strcat, mydata is %s\n", mydata);
- strcat(dest, source);
- printf("dest is %s\n", dest);
- printf("after strcat, mydata is %s\n", mydata);
- }
例子2 :
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- char dest[5] = {'D', 'D', 'D', 'D', 'D'};
- char mydata[7] = {'m','y','d','a','t','a','\0'};
- int main(void)
- {
- char i;
- char source[5];
- char bound[5] = {'&','&','&','&','&'};
- for (i = 0; i < 4; i++)
- source[i] = 'S';
- source[4] = '\0';
- printf("before strcat, mydata is %s\n", mydata);
- strcat(dest, source);
- printf("dest is %s\n", dest);
- printf("after strcat, mydata is %s\n", mydata);
- }
三. strlen 很多malloc函数缓冲区溢出问题的始作俑者 危险指数 二星
strlen是字符串求长函数,但是它求出的长度不包括‘\0’, 所以在用malloc分配内存的时候,很容易少分配一个字节,就这小小的一个字节就会造成缓冲区溢出,我们知道malloc分配的内存区域是有一个头的,这样就有可能破坏其他malloc的头使得内存释放失败,带来一系列连锁反映。因为malloc函数的实现与系统有关,这个不好用程序模拟,但是这种情况确实存在。 因此如果用strlen求字符串长度用于malloc一定要记住要加1。
四. sprintf 同样可以造成缓冲区溢出,危险指数 一星
sprintf是格式化字符拷贝函数,函数原型是int sprintf( char *buffer, const char *format, … ) 。这个函数的实现也是不安全的,使用这个函数要确保buffer足够大,否则这个函数在不做任何提示的情况下就将buffer溢出,这个函数虽然返回复制的字节数,可以通过这个检查复制了多少个字节,以确定是否缓冲区溢出。但这种亡羊补牢的做法其实没有实际意义。缓冲区溢出的错误已经发生也许会是程序崩溃,检测的时间也许都没有,就算有检测时间,也只是用于提示程序的BUG,在正式的程序中没有多大用处。
例子:
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- char dest[2] = {'D'};
- char mydata[7] = {'m','y','d','a','t','a','\0'};
- int main(void)
- {
- char i;
- char source[5];
- char bound[5] = {'&','&','&','&','&'};
- for (i = 0; i < 4; i++)
- source[i] = 'S';
- source[4] = '\0';
- printf("before sprintf, mydata is %s\n", mydata);
- sprintf(dest, "%s", source);
- printf("dest is %s\n", dest);
- printf("after sprintf, mydata is %s\n", mydata);
- }
总结
总上所述,C语言字符串操作函数一般都不对参数做检查,需要调用者确保参数的合法性。如果传入不正确的参数,就会造成缓冲区溢出。轻则数据被修改,重则程序崩溃。最郁闷的是影响到程序中不相关的部分。我前面举的例子都很简单,很容易一眼看出问题的所在,但是大型程序就不会这么简单了,这些错误就是致命的。所以使用C语言的字符串函数时一定要养成良好的习惯,自己检查参数的合法性,然后再调用。目前C语言中这些字符串操作函数都有一些安全的版本就是带n的系列,比如:strncpy,strncat,snprintf。这些函数规定了源字符串的大小,对缓冲区溢出的预防有一定的作用,比如:snprintf,其函数原型是int snprintf(char *str, size_t size, const char *format, ...) 第二个参数size,可以保证复制size个字节,如果要复制的字符串大于size就会截短,从而保证str不会溢出。程序中尽量使用这些安全的版本。良好的习惯是一个程序稳定与健壮的保证,而良好的习惯都是使用这些常用的函数养成的,所以一定要主要这些字符串函数的使用。