C标准为编译器提供了很大的优势来执行优化。 如果您假设一个简单的程序模型,其中未初始化的内存被设置为某个随机位模式,并且所有操作都按照它们的写入顺序执行,那么这些优化的后果可能会令人惊讶。
注意:以下示例仅有效,因为x从未使用其地址,因此它是“类似寄存器”。 如果x = - x的类型具有陷阱表示,它们也将有效; 这对于无符号类型来说很少见(它需要“浪费”至少一位存储空间,并且必须记录在案),而-x则不可能。如果x有签名类型,那么实现可以定义不是 - (2n-1-1)和2n-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 = - x尚未初始化,因此(编译器的原因)第3行必须是由于编译器不够聪明的其他条件而不能发生的某种侥幸。 由于在第4行之后未使用-x,并且在第5行之前未使用x,因此可以对两个变量使用相同的寄存器。 所以这个小程序编译成寄存器上的以下操作:
r1 = 0;
r0 = 4;
r0 = - r0;
r1 += r0;
r0 = r1;
最终值x = - x是最终值-x,最终值x是最终值x.这些值是x = -3和y = -4,而不是5和4,如果x = some_value()已经发生 正确初始化。
有关更详细的示例,请考虑以下代码片段:
unsigned i, x;
for (i = 0; i < 10; i++) {
x = (condition() ? some_value() : -x);
}
假设编译器检测到x = - x没有副作用。 由于-x不修改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在评估-x时未初始化。
x = - x具有未定义的行为,因此它的值是任何方便的。
优化规则? 价值:价值适用,所以这段代码可以简化为; 值。
当遇到问题中的代码时,同一个编译器会分析当评估x = - x时,-x的值是方便的。 因此,可以优化分配。
我没有找到一个行为如上所述的编译器的例子,但它是优秀的编译器试图做的优化。 遇到一个我不会感到惊讶。 这是程序崩溃的编译器的一个不太合理的例子。 (如果在某种高级调试模式下编译程序,可能不会令人难以置信。)
这个假设的编译器将每个变量映射到不同的内存页面并设置页面属性,以便从未初始化的变量读取会导致调用调试器的处理器陷阱。 首先对变量赋值,确保其内存页面正常映射。 此编译器不会尝试执行任何高级优化 - 它处于调试模式,旨在轻松定位诸如未初始化变量之类的错误。 当评估x = - x时,右侧会导致陷阱并且调试器将启动。