1.什么是数组访问越界
如果定义了一个有n个元素的数组,那么,对这n个元素(下标为0 到 n-1的元素)的访问都合法,而对这n个元素之外的空间进行访问,就是非法的,称为“越界“。又在定义数组时,数组占用的地址空间是连续的,通过指定数组下标来访问这块内存里的不同位置,当我们粗心大意,将下标取得大于等于数组的元素个数,就会访问到其它地址空间。
但是大多数编译器都不会指出你的数组访问越界了,你的代码会顺利的通过编译。数组存储区可看成是一个缓冲区,超越数组存储区范围的写入操作称为缓冲区溢出
缓冲区溢出是一种非常普遍、非常危险的漏洞,在各种操作系统、应用软件中广泛存在。
如历史上的莫里斯蠕虫: https://zh.wikipedia.org/wiki/莫里斯蠕虫
2.代码实例
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int a[2];
double d;
} struct_t;
double fun(int i){
volatile struct_t s; //类型修饰符,确保本条指令不会因编译器的优化而省略,且要求每次直接读取。
s.d = 3.14;
s.a[i] = c; /* Possibly our of bounds*/
return s.d; /* should be 3.14*/
}
int main(int argc, char *argv[]) {
int i = 0;
if(argc >= 2) //argv是参数的个数
i = atoi(argv[1]); /*atoi 将字符串转换成int型*/
double d = fun(i);
printf("fun(%d) --> %.10f\n",i,d);
return 0;
}
这段代码在Linux操作系统下运行结果如下:
在这个程序中,有一个结构体变量struct_t
typedef struct {
int a[2];
double d;
} struct_t;
s.d = 3.14;
s.a[i] = 1073741824;
该结构体在“内存”中的存储结构可以用如下图表示:
运行代码时,带的参数就是i的值,从1~6变化:
-
当参数为1,即将a[1]赋值为1073741824,即2的30次方,用十六进制表示为0x4000 0000。
此时double变量d的值还是3.14,用双精度浮点数表示为0 10000000000 10010001111010111000011…(尾数部分未全部列举出来)
输出的结果是d的值:3.14 -
当i=2,即将a[2]赋值为0x4000 0000,然而,数组a只申请了两个变量的空间,对a[2]赋值,属于越界访问了,这里实际上是将0x4000 0000写入了d的低四位,使得d的位数发生了变化,但尾数的变化对值的改变是很小的,这里输出的"d"就变成了3.1399998665
-
当i=3,将a[3]赋值为0x4000 0000,也属于越界访问,即将d的高四位赋值为0x4000 0000,将相应的浮点数转换过来,就是2.0000006104。
当i=6,就出现了存储保护出错,提示:stack smashing detected
参考链接: