未定义的行为怪癖:在缓冲区外部读取会导致循环永不终止?

Undefined Behavior quirk: reading outside a buffer causes a loop to never terminate?

我写了一个非常简单的程序来尝试检查附加到缓冲区溢出的未定义行为。具体来说,关于当您对分配的 space.

之外的数据执行读取时会发生什么
#include <iostream>
#include<iomanip>

int main() {
    int values[10];
    for (int i = 0; i < 10; i++) {
        values[i] = i;
    }

    std::cout << values << " ";
    std::cout << std::endl;
    for (int i = 0; i < 11; i++) {
        //UB occurs here when values[i] is executed with i == 10
        std::cout << std::setw(2) << i << "(" << (values + i) << "): " << values[i] << std::endl;
    }
    system("pause");
    return 0;
}

当我在 Visual Studio 上 运行 这个程序时,结果并不十分令人惊讶:读取索引 10 产生垃圾:

000000000025FD70 
 0(000000000025FD70): 0
 1(000000000025FD74): 1
 2(000000000025FD78): 2
 3(000000000025FD7C): 3
 4(000000000025FD80): 4
 5(000000000025FD84): 5
 6(000000000025FD88): 6
 7(000000000025FD8C): 7
 8(000000000025FD90): 8
 9(000000000025FD94): 9
10(000000000025FD98): -1966502944
Press any key to continue . . . 

但是当我将这个程序输入 Ideone.com 的在线编译器时,I got extremely bizarre behavior:

0xff8cac48 
0(0xff8cac48): 0
1(0xff8cac4c): 1
2(0xff8cac50): 2
3(0xff8cac54): 3
4(0xff8cac58): 4
5(0xff8cac5c): 5
6(0xff8cac60): 6
7(0xff8cac64): 7
8(0xff8cac68): 8
9(0xff8cac6c): 9
10(0xff8cac70): 1
11(0xff8cac74): -7557836
12(0xff8cac78): -7557984
13(0xff8cac7c): 1435443200
14(0xff8cac80): 0
15(0xff8cac84): 0
16(0xff8cac88): 0
17(0xff8cac8c): 1434052387
18(0xff8cac90): 134515248
19(0xff8cac94): 0
20(0xff8cac98): 0
21(0xff8cac9c): 1434052387
22(0xff8caca0): 1
23(0xff8caca4): -7557836
24(0xff8caca8): -7557828
25(0xff8cacac): 1432254426
26(0xff8cacb0): 1
27(0xff8cacb4): -7557836
28(0xff8cacb8): -7557932
29(0xff8cacbc): 134520132
30(0xff8cacc0): 134513420
31(0xff8cacc4): 1435443200
32(0xff8cacc8): 0
33(0xff8caccc): 0
34(0xff8cacd0): 0
35(0xff8cacd4): 346972086
36(0xff8cacd8): -29697309
37(0xff8cacdc): 0
38(0xff8cace0): 0
39(0xff8cace4): 0
40(0xff8cace8): 1
41(0xff8cacec): 134514984
42(0xff8cacf0): 0
43(0xff8cacf4): 1432277024
44(0xff8cacf8): 1434052153
45(0xff8cacfc): 1432326144
46(0xff8cad00): 1
47(0xff8cad04): 134514984
... 
//The heck?! This just ends with a Runtime Error after like 200 lines.

很明显,使用他们的编译器,运行单个索引超过缓冲区会导致程序进入无限循环!

现在,重申一下:我意识到我正在处理这里的未定义行为。但尽管如此,我还是想知道幕后究竟发生了什么导致了这一切。在 运行 上物理执行缓冲区的代码仍在执行 4 个字节的读取并将读取的任何内容写入(可能更好保护的)缓冲区。 compiler/CPU 做了什么导致了这些问题?

有两条执行路径导致正在评估条件 i < 11

第一个是在初始循环迭代之前。由于 i 在检查之前已被初始化为 0,因此这是平凡的。

第二个是在成功循环迭代之后。由于循环迭代导致values[i]被访问,而values只有10个元素,所以只有i < 10才有效。而如果i < 10,在i++之后,i < 11也一定为真。

这是 Ideone 的编译器 (GCC) 正在检测的内容。条件 i < 11 永远不可能为假,除非你有一个无效的程序,因此它可以被优化掉。同时,你的编译器不会特意去检查你是否有一个无效的程序,除非你提供额外的选项来告诉它这样做(比如 GCC/clang 中的 -fsanitize=undefined) .

这是实现必须做出的权衡。他们可以支持无效程序的可理解行为,或者他们可以支持有效程序的原始速度。或者两者兼而有之。 GCC 肯定非常关注后者,至少在默认情况下是这样。