zoukankan      html  css  js  c++  java
  • 你必须知道的指针基础-4.sizeof计算数组长度与strcpy的安全性问题

    一、使用sizeof计算数组长度

    1.1 sizeof的基本使用

      如果在作用域内,变量以数组形式声明,则可以使用sizeof求数组大小,下面一段代码展示了如何使用sizeof:

        int nums[] = {11,22,33,44,55,66};
        int i;
        // sizeof(nums) 计算nums数组的总字节数
        // sizeof(int) 计算int类型所占用的字节数
        int length = sizeof(nums)/sizeof(int);
        for(i=0;i<length;i++)
        {
            printf("%d ",nums[i]);
        }

      其中sizeof(nums)代表计算nums数组的总字节数,而sizeof(int)则代表计算int类型所占用的字节数(32位系统下是4个字节,64位下可能不同,因此这里使用sizeof(int)可以向程序员屏蔽这个差异),运行结果为:

    1.2 sizeof只能在编译时计算

      假如我们将上面的代码做一个抽象,将数组的遍历及打印封装为一个方法,代码如下:

    void printEach(int* nums)
    {
        // sizeof(nums)在这里是计算指针的字节数
        int length = sizeof(nums)/sizeof(int);
        printf("The length of nums is %d
    ",length);
        int i;
        for(i=0;i<length;i++)
        {
            printf("%d ",nums[i]);
        }
    }

      我们定义了一个printEach方法,其参数是一个指针,在方法内部通过sizeof计算数组长度。但是,运行结果并没有同上面的结果一致:

      我们发现,虽然我们使用了指针,但由于sizeof是编译器在编译的时候计算的,无法动态计算。因此对于int *或者将数组传递给函数,那么就无法使用sizeof获取大小了。即使函数声明中写着int[]也不行(为了避免误解,不要在参数中声明数组类型)。这里,sizeof(nums)只是计算了指针的字节数(这里指针指向了数组的首元素的地址,一个int占4个字节,所以最后length变成了1)。

      那么,为了避免出现无法计算长度的情况,我们一般都会在方法定义时增加一个长度的参数,让调用者传递过来,函数内部不再计算长度。看看如下的代码:

    void printEachWithLen(int* nums,int length)
    {
        int i;
        for(i=0;i<length;i++)
        {
            printf("%d ",nums[i]);
        }
    }

      这时候,我们就可以在main函数中调用该printEachWithLen()函数:

    int length = sizeof(nums)/sizeof(int);
    printEachWithLen(nums,length);

      这下看看结果:

      因此,一般给函数传递数组/字符串的时候都要求额外传递“长度”参数,因为函数内部也不知道“有多长”。例如:memcpy(void * restrict, const void * restrict, size_t),第三个参数size_t就是长度。又例如在.NET中,要进行数组的复制,可以使用 Array.Copy 、Buffer.BlockCopy 、Array.ConstrainedCopy等方法,通过查看其方法定义,都要求传递了数组长度。

    const int INT_SIZE = 4;
    int[] arr = { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
    Buffer.BlockCopy(arr, 3 * INT_SIZE, arr, 0 * INT_SIZE, 4 * INT_SIZE);
    foreach (int value in arr)
       Console.Write("{0}  ", value);
    // The example displays the following output:
    //  8  10  12  14  10  12  14  16  18  20    

    二、strcpy的安全性问题

    2.1 使用strcpy复制字符串

      一个简单的场景,将一个字符串复制到另一个字符串中,在C语言课本中,最长出现的就是strcpy了。我们可以轻易地写出下面的代码来实现字符串复制:

    char sourceStr[] = "hello edison";
    char destStr[30];
    strcpy(destStr,sourceStr);
    printf("%s",destStr);

      运行结果如下图所示:

      但是,我们常常听人说strcpy是不安全的函数,为什么呢?先看看strcpy内部的循环判断条件:

    while ((*strDest++ = *strSrc++) != '')

      这个循环会一直执行,直到循环条件为空,即'',也就是说,如果strDest所指的存储空间不够大的话,这个函数会将strSrc中的部分内容拷贝到strDest所指内存空间后面的内存中。而strDest所指空间后面的内存却是不可知的,有可能已经被其他资源占用了,这样就会破坏原先存储的内容,导致系统崩溃。

      因为strcpy在执行字符串拷贝的时候,会从strSrc所指位置开始,检测当前内存单元中存储的数据是否为''。如果不为'',则将这个内存单元中的数据拷贝到strDest所指向的内存中。如果strSrc中存储的字符串长度大于dst所申请的内存空间的话,就会产生越界,造成不可预知的后果。

    PS:strlen根据''判断字符串结束,那么恶意攻击者可以构造一个不包含''的字符串,然后让数据写入数组之外的程序内存空间,从而进行破坏。

    2.2 使用strncpy代替strcpy

      (1)strncpy函数定义:

    char *strncpy(char *dest, const char *src,int count)

      将字符串src中的count个字符拷贝到字符串dest中去,最后返回指向dest的指针。

      (2)strncpy用法解析:

      这个函数和strcpy类似,当src的长度大于dst申请的空间的时候,情况和strcpy一样;

      如果第3个参数count的值大于src中字符串的长度的话,就会将字符串src拷贝到dst中,返回函数。

      注意:如果源串长度大于n,则strncpy不复制最后的''结束符,所以是不安全的,复制完后需要手动添加字符串的结束符才行。

      (3)strncpy用法实例:
    char sourceStr[] = "hello edison";
    char destStr[30];
    
    int len = sizeof(sourceStr)/sizeof(char);
    printf("%d
    ",len);
    strncpy(destStr,sourceStr,len-1);
    // 保证安全的字符串复制
    destStr[len-1]='';
    printf("%s",destStr);

      运行结果如下图所示:

    参考资料

      如鹏网,《C语言也能干大事(第三版)》 

  • 相关阅读:
    Try .NET & Github Gist
    vue & font-awesome
    JSP基础与提高(一).md
    chm转换为html文件
    markdownpad生成目录
    MarkdownPad2的密钥
    删除多余的win10软件
    计算机组成原理与机构期末复习的概念
    sublime text 的小细节设置,让你的代码更优美
    NetBeans主题配色方案加设置.md
  • 原文地址:https://www.cnblogs.com/edisonchou/p/4658664.html
Copyright © 2011-2022 走看看