引言
昨天晚上写代码的时候发现个事情:代码很简单,定义变量
m
m
m,中间在循环中运算了一个数组
a
a
a,后面用到了
w
h
i
l
e
(
m
−
−
)
while(m--)
while(m−−),然后问题来了,m被改变了…纠结了很久没发现问题,后来了解到是数组下标越界产生的影响(盲点,一般数组还是老老实实开大点吧(ps.后文中数组越界是数组下标越界的简写)
今天早上把这个问题提炼出来,进行一个小小的测试吧,如下:
测试
先声明m后声明a
#include<iostream>
using namespace std;
int main()
{
int m=10;
int a[5];
//看看m和数组a的内存分配地址
cout<<"The position of m is"<<" "<<&m<<endl;
cout<<"The position of a is"<<" "<<&a<<endl;
//数组越界测试
for(int i=0;i<13;i++){
a[i]=i;
printf("a[%d]=%d,m=%d\n",i,a[i],m);
}
return 0;
}
运行结果
The position of m is 0x70fe18
The position of a is 0x70fe00
a[0]=0,m=10
a[1]=1,m=10
a[2]=2,m=10
a[3]=3,m=10
a[4]=4,m=10
a[5]=5,m=10
a[6]=6,m=6
a[7]=7,m=6
a[8]=8,m=6
a[9]=9,m=6
a[10]=10,m=6
a[11]=11,m=6
a[12]=12,m=6
a[13]=13,m=6
a[14]=14,m=6
--------------------------------
我们注意到:先声明m后声明a时,a的地址是在m之前的,那么越界到下标5时还尚未产生影响,但是越界到下标更大的时候,直接影响到后面的内存地址中的m变量!m的值自然而然被改变了。
先声明a后声明m
The position of m is0x70fdfc
The position of a is0x70fe00
a[0]=0,m=10
a[1]=1,m=10
a[2]=2,m=10
a[3]=3,m=10
a[4]=4,m=10
a[5]=5,m=10
a[6]=6,m=10
a[7]=7,m=10
a[8]=8,m=10
a[9]=9,m=10
a[10]=10,m=10
a[11]=11,m=10
a[12]=12,m=10
a[13]=13,m=10
a[14]=14,m=10
--------------------------------
我们注意到:先声明a后声明m时,m地址在前,a在后哦。那么我们发现虽然数组确确实实越界了,但是并没有对m产生影响(但我并不是说数组越界无影响 甚至没说值得推崇…
再声明一个变量交叉验证
#include<iostream>
using namespace std;
int main()
{
char ch='Z';
int a[5];
int m=10;
//看看m和数组a的内存分配地址
cout<<"The position of m is"<<" "<<&m<<endl;
cout<<"The position of a is"<<" "<<&a<<endl;
printf("The position of ch is %p\n",&ch);
//数组越界测试
for(int i=0;i<15;i++){
a[i]=i;
printf("a[%d]=%d,m=%d,ch=%c\n",i,a[i],m,ch);
}
return 0;
}
The position of m is 0x70fdfc
The position of a is 0x70fe00
The position of ch is 000000000070fe1b
a[0]=0,m=10,ch=Z
a[1]=1,m=10,ch=Z
a[2]=2,m=10,ch=Z
a[3]=3,m=10,ch=Z
a[4]=4,m=10,ch=Z
a[5]=5,m=10,ch=Z
a[6]=6,m=10,ch=
a[7]=7,m=10,ch=
a[8]=8,m=10,ch=
a[9]=9,m=10,ch=
a[10]=10,m=10,ch=
a[11]=11,m=10,ch=
a[12]=12,m=10,ch=
a[13]=13,m=10,ch=
a[14]=14,m=10,ch=
--------------------------------
论地址:m在a前,ch在a后,所以呢,ch在过度越界后产生了影响。
再交换声明顺序:
int m=10;
int a[5];
char ch='Z';
The position of a is 0x70fe00
The position of ch is 000000000070fdff
a[0]=0,m=10,ch=Z
a[1]=1,m=10,ch=Z
a[2]=2,m=10,ch=Z
a[3]=3,m=10,ch=Z
a[4]=4,m=10,ch=Z
a[5]=5,m=10,ch=Z
a[6]=6,m=6,ch=Z
a[7]=7,m=6,ch=Z
a[8]=8,m=6,ch=Z
a[9]=9,m=6,ch=Z
a[10]=10,m=6,ch=Z
a[11]=11,m=6,ch=Z
a[12]=12,m=6,ch=Z
a[13]=13,m=6,ch=Z
a[14]=14,m=6,ch=Z
--------------------------------
产生影响的也就是m了,理所应当的,如果后声明变量,则地址在先声明的变量之前,那么就不会被改变。
内存空间的分配
当然这是编译器是如何给变量分配内存空间的?这就涉及到了更底层的内存分配问题,我们常用函数体内的变量声明是以栈的结构存储的,在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的。
附上关于内存分配的参考资料:C语言变量声明内存分配
数组越界的安全问题
数组越界,不是说一定就没有内存空间可以分配用于存储数据了,而是说这段内存空间之后的地址中存储的数据是不可见的,如果越界,你输入的数据就开始在紧接着的内存单元中写,这时你有可能篡改了关键的信息…(这也是为什么被改变的m值是越界时的那个写入数据 i 的值)所以我们常用的防止数组越界的方法还是老老实实把数组开足够大(至少可以存储下我们需要存储的内容)
O J OJ OJ常见反馈: R u n t i m e E r r o r Runtime Error RuntimeError
总结
为什么数组越界会让我们一开始定义的变量值发生改变呢?
因为根据编译器的内存分配规则,存在地址的前后顺序及相邻距离的问题,如果越界后的地址是存储我们的另一个变量的,那么一定会受到影响的。
最后,还是别去随意尝试数组越界,开数组时老老实实地去操作吧!