同事有个程序出了点问题,先不谈这个,我先举个简单例子,如下:
///定义一个结构体
struct stTest
{
int nA;
int nB;
int nArray[5];
};
///仔细的看下面的调用
stTest Test;
Test.nA = 101;
Test.nB = 301;
Test.nArray[-2] = 0; ///注意数组下标为 -2
上面的代码在VC6下编译运行时都没有错误,注意在编译运行时都没有错误。
唯一的错误就是出现了意想不到结果。因为我们不想给数组下标为-2 的地方赋值。
上面的代码运行完后Test.nA的值变为 0 了。就是说给Test.nArray[-2] 赋值,其实是把值给了 Test.nA。
现在再做一个实验,定义两个全局变量,如下:
stTest Test2;
stTest Test;
然后在函数中进行如下调用:
void CDeletezsfedDlg::OnButton1()
{
Test.nA = 101;
Test.nB = 301;
Test2.nB = 701;
Test2.nA = 801;
Test.nArray[-2] = 0;
Test.nArray[6] = 0; ///注意数组下标为 6,超出了实际的数组下标
}
在VC6下的实际运行结果为
Test2.nA = 0,也就是说Test.nArray[6]的赋值操作把Test2.nA的值改了。
由此我不由想起上大学时对数组下标的争论,当时焦点是如果定义数组为
int a[10];
那么 a[10] 的访问范围是 0-9,还是 1-10,不要怪我们没有学好教材。当时做了个实验,0-9,1-10的范围都可以,哈,争论双方都无语了,访问1-10这个范围,现在想想这种做法是很危险的。还好,大家都知道数组是10个元素,还没有想超出10这个范围。
那今天上面举的例子超出了数组下标的范围,也没有出错,只是运行的结果与会与自己的预期有所不同而已。
总结下,C,C++不会对数组的下标进行严格的限制,这就需要用户自己严格把关了。
现在对上面的现象进行分析:
先看内存分配
Test 的内存地址:0x004168a0
Test.nA:0x004168a0
Test.nB:0x004168a4
Test.nArray:0x004168a8
Test2 的内存地址:0x004168c0
Test2.nA:0x004168c0
Test2.nB:0x004168c4
Test2.nArray:0x004168c8
对于第一个例子:
我们知道Test.nArray[0]的地址是0x004168a8,那么Test.nArray[-2]的地址是0x004168a8 - sizeof(int) * 2 = 0x004168a0,就是Test.nA的地址,所以如程序运行的那样把Test.nA的值改变了。
对于第二个例子:
我们知道Test.nArray[0]的地址是0x004168a8,那么Test.nArray[6]的地址是0x004168a8 + sizeof(int) * 6 = 0x004168c0,就是Test2.nA的地址,所以如程序运行的那样把Test2.nA的值改变了。
当然我同事不会简单的犯上面的错误,他是以下面的方式进行数组元素赋值的(只是举例,不代表真实的程序)
Test.nArray[nIndex],而nIndex是运算的结果,因为没有检查,所以值为-2了就出现了上面的问题,我在排查这个问题的时候也很疑惑,直到用查看内存的方式发现把 Test.nA 的值改变了,然后跟进代码发现 nIndex 为 -2 了。
那么能不能防止这个问题呢,可以,如果我们在代码中加入下面的语句
if(nIndex >= 0 && nIndex < 6)
{
Test.nArray[nIndex] = 0;
}
就可以了。
但是每一次对数组访问都这么检查会有些麻烦,那就看你怎么选择了,在安全与便利之间都是要有所取舍的。