C 标准为编译器提供了很大的自由度来执行优化。如果您假设一个天真的程序模型,其中未初始化的内存被设置为某种随机位模式并且所有操作都按照它们写入的顺序执行,那么这些优化的后果可能会令人惊讶。
注意:以下示例仅有效,因为x
它的地址从未被占用,因此它是“类寄存器”。x
如果类型有陷阱表示,它们也是有效的;对于无符号类型很少出现这种情况(它需要“浪费”至少一位存储空间,并且必须记录在案),对于unsigned char
. 如果x
具有带符号的类型,则实现可以将不是介于 -(2 n-1 -1) 和 2 n-1 -1 之间的数字的位模式定义为陷阱表示。请参阅Jens Gustedt 的回答。
编译器尝试将寄存器分配给变量,因为寄存器比内存快。由于程序使用的变量可能比处理器拥有的寄存器多,编译器执行寄存器分配,这导致不同的变量在不同的时间使用同一个寄存器。考虑程序片段
unsigned x, y, z; /* 0 */
y = 0; /* 1 */
z = 4; /* 2 */
x = - x; /* 3 */
y = y + z; /* 4 */
x = y + 1; /* 5 */
评估第 3 行时,x
还没有初始化,因此(编译器的原因)第 3 行必须是某种侥幸,由于编译器不够聪明而无法弄清楚的其他条件,所以不会发生这种侥幸。由于z
在第 4 行之后不使用,x
也不在第 5 行之前使用,因此两个变量可以使用同一个寄存器。所以这个小程序被编译成对寄存器进行如下操作:
r1 = 0;
r0 = 4;
r0 = - r0;
r1 += r0;
r0 = r1;
的最终值x
是 的最终值r0
, 的最终值y
是 的最终值r1
。这些值是 x = -3 和 y = -4,而不是 5 和 4,如果x
正确初始化会发生这种情况。
对于更详细的示例,请考虑以下代码片段:
unsigned i, x;
for (i = 0; i < 10; i++) {
x = (condition() ? some_value() : -x);
}
假设编译器检测到condition
没有副作用。由于condition
不修改x
,编译器知道循环的第一次运行不可能被访问x
,因为它还没有初始化。因此循环体的第一次执行相当于x = some_value()
,不需要测试条件。编译器可能会编译此代码,就像您编写的一样
unsigned i, x;
i = 0; /* if some_value() uses i */
x = some_value();
for (i = 1; i < 10; i++) {
x = (condition() ? some_value() : -x);
}
这可以在编译器内部建模的方式是考虑任何依赖的值只要未初始化就x
具有方便的值。x
因为未定义变量时的行为,而不是仅具有未指定值的变量,所以编译器不需要跟踪任何方便值之间的任何特殊数学关系。因此编译器可以这样分析上面的代码:
- 在第一次循环迭代期间,被评估
x
的时间未初始化。-x
-x
具有未定义的行为,因此它的值是方便的。
- 优化规则适用,因此此代码可以简化为.
condition ? value : value
condition; value
当遇到您问题中的代码时,同一个编译器会分析,在x = - x
评估时, 的值-x
是方便的。因此可以优化分配。
我还没有寻找表现如上所述的编译器的示例,但它是优秀编译器尝试做的那种优化。遇到一个我不会感到惊讶。这是您的程序崩溃的编译器的一个不太合理的示例。(如果你在某种高级调试模式下编译你的程序,这可能不是那么令人难以置信。)
这个假设的编译器将每个变量映射到不同的内存页面并设置页面属性,以便从未初始化的变量中读取会导致调用调试器的处理器陷阱。对变量的任何赋值首先确保其内存页被正常映射。此编译器不会尝试执行任何高级优化——它处于调试模式,旨在轻松定位诸如未初始化变量之类的错误。评估时x = - x
,右侧会导致陷阱并且调试器会启动。