C语言字符串
一、字符串结束标志
在讨论C语言中缓冲区溢出问题时,很多溢出问题是由字符串和字符串的相关库函数引起的,涉及最多的就是字符串结尾标识符’\0’,所以,C语言的字符串为什么以0作为它的结尾?
因为c语言中没有字符串类型,所以借助字符数组来存储字符串,为了区别字符串,就需要特殊标记。而在一般的字符集中,0一般保留不用,因此C用0来作为字符串的结束标志,它占用存储空间,但不记入字符串的实际长度。所以我们用sizeof关键字和strlen计算同一个字符串所得结果相差1,这个1就是结束标志0.字符串长度和所占内存大小例子程序:
void testString() { //initial string in different way //and then test their length and memory storage volume char str1[] = "i love you"; char * str2 = "i love you"; char str3[] = { 'i',' ','l','o','v','e',' ','y','o','u'}; printf("str1: %d %d\n", strlen(str1), sizeof(str1)); printf("str2: %d %d\n", strlen(str2), sizeof(str2)); //输出 10 4 //我明白4是指针占4字节的内存,所以我就改为 printf("str2: %d %d\n", strlen(str2), sizeof(*str2)); //输出 10 1 //我就奇怪了,怎么还输出1了 //*str2是str2所指向的位置的内容,的确是一个字符,没有错 //如果要指向下一个字符,还需要移动指针。。。 printf("str2: %d %d\n", strlen(str2), sizeof("i love you")); //ooutput 10 11 printf("str3: %d %d\n", strlen(str3), sizeof(str3)); //第一次输出42 10; 第二次输出23 10 //开始,我以为是怎么回事呢,最后才明白,这就是栈溢出,这就是漏洞 //str3是一个存储字符的数组,但是它不是一个字符串常量,所以不会有自动加‘\0’的操作 //所以当我们将str3用strlen遍历计数时,就会一直读出数组之外,直到遇到‘\0’ //所以在实时运行时才会因为内存内容的变化,输出不同的数字 //所以对于没有‘\0’做结束的字符数组,我们是不能使用像strlen这样用于字符串的函数的 //而应该使用length = zizeof(str3)/sizeof(char); }
二、字符串和内存块复制函数
1. strcpy()
头文件:#include <string.h>
函数原型:char *strcpy(char *dest, const char *src);
函数说明:把从src地址开始且含有'\0'结束符的字符串赋值到以dest开始的地址空间
返回值:返回参数dest 的字符串起始地址。
附加说明:如果参数 dest 所指的内存空间不够大,可能会造成缓冲溢出(buffer Overflow)的错误情况,在编写程序时请特别留意,或者用strncpy()来取代。
该函数的参数是字符指针,也就是可以是字符串变量和字符数组,因为它们的变量名代表首字符地址。字符串默认有一个'\0'结束符,字符数组没有。所以此处需要注意:因为src要求有'\0'结束符,所以字符数组的长度必须大于等于src包含'\0'结束符的总长度。例如,char* src="abcd"; char dest[5]; 这里dest的长度就至少为5。
2. strncpy()
原型:char * strncpy(char *dest, char *src, size_t n);
功能:将字符串src中最多n个字符复制到字符数组dest中,它并不像strcpy一样遇到'\0'就停止复制,而是在遇到’\0’后如果不足n个字符,就会将后面全部置为0,直到凑够n个字节。如果在遇到’\0’前就凑够n个字符,也结束复制,这时候因为末尾没有’\0’结束符,在读取时就会产生缓冲区溢出错误。
下面是一个比较以上两个函数的例子:
//这个函数是为了测试strcpy和strncpy的不同之处 //前者将src复制到dest,遇到‘\0’就停止 //后者遇到'\0'后,将后面所有都置为'\0' //所以看到,str3后面会有原先初始化的A,但是str2在字符串结束后,全部是空格 int testStrncpy() { char str1[] = "To be or not to be"; //18 bytes without '\0' char str2[40]; char str3[40]; memset(str2, 65, 40); memset(str3, 65, 40); /*for (int i = 0; i < 40; i++) { str2[i] = 'a'; str3[i] = 'a'; }*/ /* copy to sized buffer (overflow safe): */ strncpy(str2, str1, sizeof(str2)); strcpy(str3, str1); printf("str2:\n"); for (int i = 0; i < 40; i++) { printf("%c", str2[i]); } printf("str3:"); for (int i = 0; i < 40; i++) { printf("%c", str3[i]); } /* partial copy (only 5 chars): */ //strncpy(str3, str2, 5); //str3[5] = '\0'; /* null character manually added */ //puts(str1); //puts(str2); //puts(str3); return 0; }
3. memcpy()
头文件:#include<string.h>
函数原型:void *memcpy(void* dest, const void *src, size_t n);
返回值:函数返回一个指向dest的指针。
功能:由src指向地址为起始地址的连续n个字节的数据复制到以dest指向地址为起始地址的空间内。
说明:
1.source和destination所指内存区域不能重叠,函数返回指向destination的指针。
2.与strcpy相比,memcpy并不是遇到'\0'就结束,而是一定会拷贝完n个字节。memcpy用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度;例:
char a[100], b[50]; memcpy(b, a,sizeof(b)); //注意如用sizeof(a),会造成b的内存地址溢出。 //strcpy就只能拷贝字符串了,它遇到'\0'就结束拷贝;例: char a[100], b[50]; strcpy(a,b);
3.如果目标数组destin本身已有数据,执行memcpy()后,将覆盖原有数据(最多覆盖n)。如果要追加数据,则每次执行memcpy后,要将目标数组地址增加到你要追加数据的地址。
注意,source和destin都不一定是数组,任意的可读写的空间均可。
下面是三个例子程序:
int testMemcpy() { char* s = "Golden Global View"; //19 bytes if added '/0' printf("%d\n", strlen(s)); //output:18 char d[20]; memcpy(d, s, strlen(s)); d[strlen(s)] = '\0';//因为从d[0]开始复制,总长度为strlen(s),d[strlen(s)]置为结束符 printf("%s", d); return 0; } int laterMemcpy() { char*s = "Golden Global View"; char d[20]; memcpy(d, s + 14, 4);//从第14个字符(V)开始复制,连续复制4个字符(View) //memcpy(d,s+14*sizeof(char),4*sizeof(char));也可 d[4] = '\0'; printf("%s\n", d); return 0; } int coverMemcpy() { char src[] = "******************************"; char dest[] = "abcdefghijlkmnopqrstuvwxyz0123as6"; printf("destinationbefore memcpy: %s\n", dest); memcpy(dest, src, strlen(src)); printf("destinationafter memcpy: %s\n", dest); return 0; }
4. memmove()
void * memmove ( void * destination, const void * source, size_t num );
功能等基本和memcpy相同,但它允许src和destination重叠(overlap) 例如:
/* memmove example */ #include <stdio.h> #include <string.h> int main () { char str[] = "memmove can be very useful......"; memmove (str+20,str+15,11); puts (str); return 0; } //output: //memmove can be very very useful.
三、总结
1.strcpy是依据’\0’作为结束判断的,如果destination的空间不够,则会引起 buffer overflow。
2.strncpy是依据’\0’和n作为结束判断的,destination一定会被写入n个字符,n个字符是否含有字符串结束标志,取决于遇到src中’\0’时是否筹够了n个字符。
3.memcpy用来在内存中复制数据,由于字符串是以“\0”结尾的,所以对于在数据中包含“\0”的数据只能用memcpy。strcpy和strncpy都不能完整复制数据。
4.strncpy和memcpy很相似,只不过它在’\0’处停止。
所以总的来说使用时注意:
1.destination指向的空间要足够拷贝;使用strcpy时,destination指向的空间要大于等于src指向的空间;使用strncpy或memcpy时,destination指向的空间要大于或等于n。
2.使用strncpy或memcpy时,n应该大于strlen(src),或者说最好n >= strlen(src)+1;这个1 就是最后的’\0’。
3.使用strncpy时,确保destination的最后一个字符是’\0’。
参考文章:
http://blog.csdn.net/tigerjibo/article/details/6841531
http://c.biancheng.net/cpp/html/164.html
http://www.cplusplus.com/reference/cstring/strncpy/
http://blog.csdn.net/taric_ma/article/details/7383713