zoukankan      html  css  js  c++  java
  • C语言字符串

    一、字符串结束标志

    在讨论C语言中缓冲区溢出问题时,很多溢出问题是由字符串和字符串的相关库函数引起的,涉及最多的就是字符串结尾标识符’’,所以,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
    ", strlen(str1), sizeof(str1));
        printf("str2: %d %d
    ", strlen(str2), sizeof(str2));
        //输出 10 4
        //我明白4是指针占4字节的内存,所以我就改为
        printf("str2: %d %d
    ", strlen(str2), sizeof(*str2));
        //输出 10 1
        //我就奇怪了,怎么还输出1了
        //*str2是str2所指向的位置的内容,的确是一个字符,没有错
        //如果要指向下一个字符,还需要移动指针。。。
        printf("str2: %d %d
    ", strlen(str2), sizeof("i love you"));
        //ooutput 10 11
        printf("str3: %d %d
    ", strlen(str3), sizeof(str3));
        //第一次输出42 10; 第二次输出23 10
        //开始,我以为是怎么回事呢,最后才明白,这就是栈溢出,这就是漏洞
        //str3是一个存储字符的数组,但是它不是一个字符串常量,所以不会有自动加‘’的操作
        //所以当我们将str3用strlen遍历计数时,就会一直读出数组之外,直到遇到‘’
        //所以在实时运行时才会因为内存内容的变化,输出不同的数字
        //所以对于没有‘’做结束的字符数组,我们是不能使用像strlen这样用于字符串的函数的
        //而应该使用length = zizeof(str3)/sizeof(char);
    }

    二、字符串和内存块复制函数

    1. strcpy()

    头文件:#include <string.h>

    函数原型:char *strcpy(char *dest, const char *src);

    函数说明:把从src地址开始且含有''结束符的字符串赋值到以dest开始的地址空间

    返回值:返回参数dest 的字符串起始地址。

    附加说明:如果参数 dest 所指的内存空间不够大,可能会造成缓冲溢出(buffer Overflow)的错误情况,在编写程序时请特别留意,或者用strncpy()来取代。

    该函数的参数是字符指针,也就是可以是字符串变量和字符数组,因为它们的变量名代表首字符地址。字符串默认有一个''结束符,字符数组没有。所以此处需要注意:因为src要求有''结束符,所以字符数组的长度必须大于等于src包含''结束符的总长度。例如,char* src="abcd"; char dest[5]; 这里dest的长度就至少为5。

    2. strncpy()

    原型:char * strncpy(char *dest, char *src, size_t n);  

    功能:将字符串src中最多n个字符复制到字符数组dest中,它并不像strcpy一样遇到''就停止复制,而是在遇到’’后如果不足n个字符,就会将后面全部置为0,直到凑够n个字节。如果在遇到’’前就凑够n个字符,也结束复制,这时候因为末尾没有’’结束符,在读取时就会产生缓冲区溢出错误。

    下面是一个比较以上两个函数的例子:

    //这个函数是为了测试strcpy和strncpy的不同之处
    //前者将src复制到dest,遇到‘’就停止
    //后者遇到''后,将后面所有都置为''
    //所以看到,str3后面会有原先初始化的A,但是str2在字符串结束后,全部是空格
    int testStrncpy()
    {
        char str1[] = "To be or not to be";  //18 bytes without ''
        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:
    ");
        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] = '';   /* 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并不是遇到''就结束,而是一定会拷贝完n个字节。memcpy用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度;例:

    char a[100], b[50];
    memcpy(b, a,sizeof(b)); //注意如用sizeof(a),会造成b的内存地址溢出。
    //strcpy就只能拷贝字符串了,它遇到''就结束拷贝;例:
    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
    ", strlen(s));       //output:18
        char d[20];
        memcpy(d, s, strlen(s));
        d[strlen(s)] = '';//因为从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] = '';
        printf("%s
    ", d);
        return 0;
    }
    
    int coverMemcpy()
    {
        char src[] = "******************************";
        char dest[] = "abcdefghijlkmnopqrstuvwxyz0123as6";
        printf("destinationbefore memcpy: %s
    ", dest);
        memcpy(dest, src, strlen(src));
        printf("destinationafter memcpy: %s
    ", 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是依据’’作为结束判断的,如果destination的空间不够,则会引起 buffer overflow。

    2.strncpy是依据’’和n作为结束判断的,destination一定会被写入n个字符,n个字符是否含有字符串结束标志,取决于遇到src中’’时是否筹够了n个字符。

    3.memcpy用来在内存中复制数据,由于字符串是以“”结尾的,所以对于在数据中包含“”的数据只能用memcpy。strcpy和strncpy都不能完整复制数据。

    4.strncpy和memcpy很相似,只不过它在’’处停止。

    所以总的来说使用时注意:

    1.destination指向的空间要足够拷贝;使用strcpy时,destination指向的空间要大于等于src指向的空间;使用strncpy或memcpy时,destination指向的空间要大于或等于n。

    2.使用strncpy或memcpy时,n应该大于strlen(src),或者说最好n >= strlen(src)+1;这个1 就是最后的’’。

    3.使用strncpy时,确保destination的最后一个字符是’’。

     

     

     

    参考文章:

    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

     

     

     

  • 相关阅读:
    JS 百度地图路书---动态路线
    jQuery---创建和添加节点
    CSS基础
    第一篇:前端知识之HTML内容
    JS高级---为内置对象添加原型方法
    JS DOM属性+JS事件
    Vue-router
    vue使用kkfileview文件预览功能
    JS高级---案例:验证密码的强度
    promise是怎么来的?
  • 原文地址:https://www.cnblogs.com/wangyuxia/p/6719381.html
Copyright © 2011-2022 走看看