#include <stdio.h>
#include <stdlib.h>
int main() {
int step;
double position[4];
position[0] = 1;
for (step = 1;step<=4;step++){
position[step] = 99;
}
return 0;
}
可以编译而不会出错,生成的程序可以运行。
然而
#include <stdio.h>
#include <stdlib.h>
int main() {
int step;
double position[3];
position[0] = 1;
for (step = 1;step<=3;step++){
position[step] = 99;
}
return 0;
}
也可以编译,但程序无法运行:错误Abort trap: 6
。
在上述两种情况下,(错误)初始化的数组的大小比我在 for 循环中填写的大小小 1。但是,为什么4
和3
在这里有所作为呢?
现在,更有趣的是,
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
int main() {
int step;
double position[4];
position[0] = 1;
position[1] = 99;
position[2] = 99;
position[3] = 99;
position[4] = 99;
return 0;
}
甚至无法编译(错误为array index 4 is past the end of the array (which contains 4 elements
)。那么为什么 for 循环在这里会有所不同呢?
在第三种情况下,编译器会提醒您越界访问。该标准不要求它抱怨,但它这样做了。
对于前两种情况,没有必要考虑正在发生的事情。你说第一个程序运行良好。它没有 - 它有一个 UB。
对于你关于3和4如何改变任何东西的问题,它可能取决于堆栈帧的布局方式。由于对齐方式差异,退货地址可能在一种情况下被弄乱,但在另一种情况下不会。您必须查看生成的程序集文件,以了解实际出了什么问题。
此 https://godbolt.org/g/gqz39q 表明,如果您将数组大小设置为 3,它将position
放在%rbp - 32
处,step
放在%rbp - 4
处。所以万一当你写position[3]
时,step
被覆盖了(我不想考虑正在写什么)。
当您将position
的大小设为 4 时,它会将step
置于%rbp - 4
,position
置于%rbp - 48
。现在你写信给position[4]
即%rbp - 48 + 4 * 8 = %rbp - 16
.这将写入%rbp - 8
.所以%rbp - 4
(step
)没有改变。
长话短说,填充在案例 1 中拯救了您,但在案例 2 中没有。
PS:同样,这是特定于所选编译器gcc 6.2与O0优化级别。在您的情况下,原因可能完全不同。
C 语言中没有提到任何内容可以阻止您编写访问越界内存的代码,标准只是清楚地提到任何这样做的尝试都会导致未定义的行为。
任何诊断,如果提供由编译器自行决定,可能与提供的编译器选项相关联,标准没有提到这一点的要求。
例如,对于某些编译器,最后一个代码段编译得很好(并且也会收到运行时错误)。
注1:在显示的代码段中,语句不是初始化,而是赋值。
注意2:我稍微修改了代码,但所做的无效访问是相同的
#include <stdio.h>
int main(void) {
//int step; // remove unused variable warning
double position[4];
position[0] = 1;
position[1] = 99;
position[2] = 99;
position[3] = 99;
position[4] = 99;
(void) position; // suppress unused variable warning
return 0;
}