你必须知道的指针基础-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\n",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++) != ‘\0‘)

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

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

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

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不复制最后的‘\0‘结束符,所以是不安全的,复制完后需要手动添加字符串的结束符才行。

  (3)strncpy用法实例:

char sourceStr[] = "hello edison";
char destStr[30];

int len = sizeof(sourceStr)/sizeof(char);
printf("%d\n",len);
strncpy(destStr,sourceStr,len-1);
// 保证安全的字符串复制
destStr[len-1]=‘\0‘;
printf("%s",destStr);

  运行结果如下图所示:

参考资料

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

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

时间: 2024-08-25 22:25:27

你必须知道的指针基础-4.sizeof计算数组长度与strcpy的安全性问题的相关文章

妙用sizeof获得数组长度

总所周知,sizeof关键字返回一个类型的长度.于是,一些聪明的人就想出了用sizeof(array) / sizeof(array[0])来获取array数组的长度.这个表达式十分巧妙.第一个sizeof(array)返回array的长度,第二个sizeof(array[0])返回array每个元素的类型的长度.将数组的总长除以其中每个元素的长度,就可以得出该数组的长度. 写个小程序试一下,可行.那么,指针数组行不行呢? 咦,这次怎么失败了?用printf看一下. int类型的array[0]

sizeof与数组长度

代码: #include <stdio.h> #include <stdlib.h> #define LENGTH 10 void func(int arr[]); int main(void) { int arr[LENGTH] = { 0 }; // sizeof 数组名:整个数组大小 // sizeof 数组名[0]:数组第一个元素大小 size_t length = sizeof arr / sizeof arr[0]; printf(length == LENGTH ? 

利用指针地址偏移打印一维数组

// //  main.c //  利用指针地址偏移打印一维数组 // // Created by wanghy on 15/7/24. // Copyright (c) 2015年 wanghy. All rights reserved. // #include <stdio.h> #define len 10 int main(int argc, const char * argv[]){ //定义一个存放  int 类型元素的一维数组. int arry[]={1,2,3,4,5,6,7

sizeof() 之 数组

在平时的编程中,我们会经常用到数组,并且需要知道数组的长度,有时我们可以明确的知道数组的长度,但有时并不,这时,可以借用sizeof(),来获得数组的长度,如下: arrayLength = sizeof(array) / sizeof(array[0]); 在使用sizeof() 获得数组长度时,需要注意,如果数组array 和 sizeof() 的使用 是在同一个文件中,那么array在定义时,不需要明确数组长度:如果他们在不同的文件中时,则array在定义时必须明确长度,否则会报错 “ i

【C++基础】sizeof 数组 指针 空NULL

笔试遇到很多sizeof的小题,博主基础堪忧,怒总结如下,还是要巩固基础啊啊啊! sizeof操作符 对象所占内存空间的大小,单位是字节 关键词:char  数组 指针 结构体 sizeof(NULL) 结果为1 1.基本类型占内存大小   32位机     64位机器 类型 字节数       int 4       char 1       指针 4     8 float 4 浮点型     long 4       double 8 双精度浮点型     2.sizeof(指针)  任意

C语言--&gt;(十)指针基础

知识点: 1.指针基础 2.指针和函数(地址传递) ======================================= 指针是C中的一个重要的概念,也是C的一个重要特色.掌握指针的应用,可以使程序简洁.紧凑.高效. 指针的概念比较复杂,使用也比较灵活,因此初学时会常出错,我们要理解每一个概念的本质,多加练习,在实践中掌握它. 程序使用指针和没有没有使用指针的代码是两个档次. ========================================指针基础 [地址概念] 1.生

C++中的指针基础

C++ -- 指针 1.指针基础 指针是一个变量,其存储的是值的地址,并不是值本身.要获得一个变量的地址,要通过地址运算符&实现. eg: int a = 4; int *p = &a; 这里指针p就存储了a的内存地址,p指向变量a的地址.指针表示一个变量地址,要想通过指针获取值,需要*运算符,称为间接值或解除引用,将其应用于指针,可以获取该地址存储的值.如*p,*符号后面必须是指针. #include <iostream> using namespace std; int m

【C++基础】sizeof 与 strlen的区别

要理解两者的区别,就要分别理解他们的本质 strlen(char *) 计算字符串的长度,内部实现是用一个循环计算字符串的长度,直到‘\0’为止 1.srtlen 是一个函数,参数只能为char 或者 string (sizeof是运算符,可以对好多类型作sizeof,如struct, class等) 2.对数组作sizeof不退化为指针,传递给strlen就退化为指针 3. char ss[100]="012345467"; cout << strlen(ss) <

8.30 指针基础概念

指针的兼容性 sizeof的结果是一样的就是兼容的. int *p; unsigned int ui = 10; p = &ui;  //正确 指针相加: 不是以整数的形式增加,而是移动 数组名与指针: 函数应该传数组名 : #include <stdio.h> void func(int *a) { a += 3; *a = 100; } int main() { int a[10] = {1,2,3,4,5,6,7,8,9,0}; int i; func(&a);//这里传