数组下标取值越界与指向数组的指针的指向范围越界
。int a[3]
,它的下标取值范围是[0,2]
(即a[0]、a[1] 与 a[2]
)。如果我们的取值不在这个范围内(如 a[3]),就会发生越界错误。示例代码如下所示:1int a[3];
2int i=0;
3for(i=0;i<4;i++)
4{
5 a[i] = i;
6}
7for(i=0;i<4;i++)
8{
9 printf("a[%d]=%d\n",i,a[i]);
10}
1int a[3];
2int i=0;
3for(i=0;i<3;i++)
4{
5 a[i] = i;
6}
7for(i=0;i<3;i++)
8{
9 printf("a[%d]=%d\n",i,a[i]);
10}
1int i;
2int *p;
3int a[5];
4/*数组a的头指针赋值给指针p*/
5p=a;
6for(i=0;i<10;i++)
7{
8 /*指针p指向的变量*/
9 *p=i+10;
10 /*指针p下一个变量*/
11 p++;
12}
[0,4]
(即 a[0]、a[1]、a[2]、a[3] 与 a[4]
)。因此,后 5 次的操作会对未知的内存区域赋值,而这种向内存未知区域赋值的操作会使系统发生错误。正确的操作应该是指针移动的次数与数组中的变量个数相同,如下面的代码所示:1int i;
2int *p;
3int a[5];
4/*数组a的头指针赋值给指针p*/
5p=a;
6for(i=0;i<5;i++)
7{
8 /*指针p指向的变量*/
9 *p=i+10;
10 /*指针p下一个变量*/
11 p++;
12}
1#define PASSWORD "123456"
2int Test(char *str)
3{
4 int flag;
5 char buffer[7];
6 flag=strcmp(str,PASSWORD);
7 strcpy(buffer,str);
8 return flag;
9}
10int main(void)
11{
12 int flag=0;
13 char str[1024];
14 while(1)
15 {
16 printf("请输入密码: ");
17 scanf("%s",str);
18 flag = Test(str);
19 if(flag)
20 {
21 printf("密码错误!\n");
22 }
23 else
24 {
25 printf("密码正确!\n");
26 }
27 }
28 return 0;
29}
123456
进行比较。很显然,本示例中最大的设计漏洞就在于 Test() 函数中的 strcpy(buffer,str)
调用。char buffer[7]
中。因此,当用户的输入大于 7 个字符的缓冲区尺寸时,就会发生数组越界错误,这也就是大家所谓的缓冲区溢出Buffer overflow 漏洞。1请输入密码:12345
2密码错误!
3请输入密码:123456
4密码正确!
5请输入密码:1234567
6密码正确!
7请输入密码:aaaaaaa
8密码正确!
9请输入密码:0123456
10密码错误!
11请输入密码:
密码错误
的流程(非 0)还是“密码正确”的流程(0)。当我们输入错误的字符串1234567
或者aaaaaaa
,程序也都会输出“密码正确”。但在输入0123456
的时候,程序却输出“密码错误”,这究竟是为什么呢?char buffer[7]
与 int flag
将会紧挨着进行存储,用户输入的字符串将会被复 制进 buffer[7] 中。如果这个时候,我们输入的字符串数量超过 6 个(注意,有字符串截断符也算一个),那么超出的部分将破坏掉与它紧邻着的 flag 变量的内容。123456
时,字符串比较将返回 1 或 -1。我们都知道,内存中的数据按照 4 字节(DWORD)逆序存储,所以当 flag 为 1 时,在内存中存储的是0x01000000
。如果我们输入包含 7 个字符的错误密码,如aaaaaaa
,那么字符串截断符 0x00 将写入 flag 变量,这样溢出数组的一个字节 0x00 将恰好把逆序存放的 flag 变量改为 0x00000000
。在函数返回后,一旦 main 函数的 flag 为 0,就会输出“密码正确”。这样,我们就用错误的密码得到了正确密码的运行效果。0123456
,因为在进行字符串的大小比较时,它小于123456
,flag的值是 -1,在内存中将按照补码存放负数,所以实际存储的不是 0x01000000
而是 0xffffffff
。那么字符串截断后符 0x00 淹没后,变成 0x00ffffff
,还是非 0,所以没有进入正确分支。1int a[]={1,2,3,4,5,6,7,8,9,10};
1int a[10]={1,2,3,4,5,6,7,8,9,10};
1#define MAX 10
2…
3int a[MAX]={1,2,3,4,5,6,7,8,9,10};
1int a[MAX]={1,2,3,4,5,[MAX-5]=6,7,8,9,10};
a[MAX]
数组中,如果 MAX 大于 10,数组中间将用 0 值元素进行填充(填充的个数为 MAX-10
,并从 a[5] 开始进行 0 值填充);如果 MAX 小于 10,[MAX-5]
之前的 5 个元素(1,2,3,4,5)
中将有几个被[MAX-5]
之后的 5 个元素(6,7,8,9,10)
所覆盖,示例代码如下所示:1#define MAX 10
2#define MAX1 15
3#define MAX2 6
4int main(void)
5{
6 int a[MAX]={1,2,3,4,5,[MAX-5]=6,7,8,9,10};
7 int b[MAX1]={1,2,3,4,5,[MAX1-5]=6,7,8,9,10};
8 int c[MAX2]={1,2,3,4,5,[MAX2-5]=6,7,8,9,10};
9 int i=0;
10 int j=0;
11 int z=0;
12 printf("a[MAX]:\n");
13 for(i=0;i<MAX;i++)
14 {
15 printf("a[%d]=%d ",i,a[i]);
16 }
17 printf("\nb[MAX1]:\n");
18 for(j=0;j<MAX1;j++)
19 {
20 printf("b[%d]=%d ",j,b[j]);
21 }
22 printf("\nc[MAX2]:\n");
23 for(z=0;z<MAX2;z++)
24 {
25 printf("c[%d]=%d ",z,c[z]);
26 }
27 printf("\n");
28 return 0;
29}
1a[MAX]:
2a[0]=1 a[1]=2 a[2]=3 a[3]=4 a[4]=5 a[5]=6 a[6]=7 a[7]=8 a[8]=9 a[9]=10
3b[MAX1]:
4b[0]=1 b[1]=2 b[2]=3 b[3]=4 b[4]=5 b[5]=0 b[6]=0 b[7]=0 b[8]=0 b[9]=0 b[10]=6 b[11]=7 b[12]=8 b[13]=9 b[14]=10
5c[MAX2]:
6c[0]=1 c[1]=6 c[2]=7 c[3]=8 c[4]=9 c[5]=10
1#define ARRAY_NUM 10
2int *TestArray(int num,int value)
3{
4 int *arr=NULL;
5 arr=(int *)malloc(sizeof(int)*ARRAY_NUM);
6 if(arr!=NULL)
7 {
8 arr[num]=value;
9 }
10 else
11 {
12 /*处理arr==NULL*/
13 }
14 return arr;
15}
int*TestArray(int num,int value)
函数中不难看出,其中存在着一个很明显的问题,那就是无法保证 num 参数是否越界(即当num>=ARRAY_NUM
的情况)。因此,应该对 num 参数进行越界检查,示例代码如下所示:1int *TestArray(int num,int value)
2{
3 int *arr=NULL;
4 /*越界检查(越上界)*/
5 if(num<ARRAY_NUM)
6 {
7 arr=(int *)malloc(sizeof(int)*ARRAY_NUM);
8 if(arr!=NULL)
9 {
10 arr[num]=value;
11 }
12 else
13 {
14 /*处理arr==NULL*/
15 }
16 }
17 return arr;
18}
if(num<ARRAY_NUM)
语句进行越界检查,从而保证 num 参数没有越过这个数组的上界。现在看起来,TestArray() 函数应该没什么问题,也不会发生什么越界错误。if(num<ARRAY_NUM)
语句里面再加一个条件进行测试,如下面的代码所示:1if(num>=0&&num<ARRAY_NUM)
2{
3}
1int *TestArray(size_t num,int value)
2{
3 int *arr=NULL;
4 /*越界检查(越上界)*/
5 if(num<ARRAY_NUM)
6 {
7 arr=(int *)malloc(sizeof(int)*ARRAY_NUM);
8 if(arr!=NULL)
9 {
10 arr[num]=value;
11 }
12 else
13 {
14 /*处理arr==NULL*/
15 }
16 }
17 return arr;
18}
int a[5]
,可以使用sizeof(a)
来获取数组的长度,使用sizeof(a[0])
来获取数组元素的长度。1/*若此时max定义为intmax();*/
2sizeof(max)
3/*若此时arr定义为char arr[MAX],且MAX未知*/
4sizeof(arr)
5/*不能够用于void类型*/
6sizeof(void)
7/*不能够用于位字段*/
8struct S
9{
10 unsigned int f1 : 1;
11 unsigned int f2 : 5;
12 unsigned int f3 : 12;
13};
14sizeof(S.f1);
1void Init(int arr[])
2{
3 size_t i=0;
4 for(i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
5 {
6 arr[i]=i;
7 }
8}
9int main(void)
10{
11 int i=0;
12 int a[10];
13 Init(a);
14 for(i=0;i<10;i++)
15 {
16 printf("%d\n",a[i]);
17 }
18 return 0;
19}
0,1,2,3,4,5,6,7,8,9
,但实际结果却出乎我们的意料,如图 1 所示。void Init(int arr[])
函数中接收了一个int arr[]
类型的形参,并且在main函数中向它传递一个a[10]
实参。同时,在 Init() 函数中通过sizeof(arr)/sizeof(arr[0])
来确定这个数组元素的数量和初始化值。sizeof(arr)=sizeof(int*)
。在 IA-32 中,sizeof(arr)/sizeof(arr[0])
的结果为 1。因此,最后的结果如图 1 所示。1void Init(int arr[],size_t arr_len)
2{
3 size_t i=0;
4 for(i=0;i<arr_len;i++)
5 {
6 arr[i]=i;
7 }
8}
9int main(void)
10{
11 int i=0;
12 int a[10];
13 Init(a,10);
14 for(i=0;i<10;i++)
15 {
16 printf("%d\n",a[i]);
17 }
18 return 0;
19}
1void Init(int (*arr)[10])
2{
3 size_t i=0;
4 for(i=0;i< sizeof(*arr)/sizeof(int);i++)
5 {
6 (*arr)[i]=i;
7 }
8}
9int main(void)
10{
11 int i=0;
12 int a[10];
13 Init(&a);
14 for(i=0;i<10;i++)
15 {
16 printf("%d\n",a[i]);
17 }
18 return 0;
19}
arr[10]
类型的指针。需要特别注意的是,这里绝对不能够使用void Init(int(*arr)[])
来声明函数,而是必须指明要传入的数组的大小,否则sizeof(*arr)
无法计算。但是在这种情况下,再通过 sizeof 来计算数组大小已经没有意义了,因为此时数组大小已经指定为 10 了。------------ END ------------
素材来源:嵌入式ARM