关于 OOO,假设我只有一个进程(带有一个线程)运行此代码:
void foo() {
if (x == 0) {
return;
}
y->data = 5;
}
现在,让我们假设我知道,仅当
y
不为零时,x
才有效。
从硬件角度来看,CPU可以在读取y->data = 5
之前执行x
吗?
它可能会导致 CPU 访问 NULL/垃圾指针并崩溃。
如果不是,原因是什么? 因为 if/while/for/goto 是控制语句,CPU 看到控制语句时不会提前获取指令?
我记得,OOO 对于执行其指令的一个线程应该是 100% 透明的。
取决于你如何看待它。
从用户的角度来看,程序的行为必须“好像”是按顺序运行的。
换句话说,顺序运行和使用 OOE 运行之间没有明显的区别。 (除了性能之外)
从CPU的角度来看,是的,它实际上可以绕过if语句并执行
y->data = 5;
。但这是因为分支预测而不是OOE。
在现代处理器上,线程有可能错误预测分支:
if (x == 0) {
return;
}
并实际尝试执行
y->data = 5;
...
如果发生这种情况并且
y
是一个坏指针,它将得到硬件异常,但由于执行仍处于推测模式,所以该异常被保留。
一旦线程意识到它错误地预测了分支,它将丢弃该分支之后的所有内容(包括异常)。
所以最终没有什么可担心的。即使处理器尝试做它不能做的事情,也不会影响顺序行为。
换句话说,如果现代处理器弄得一团乱,这不是你的错,它会自行清理。
当您有多个线程时,事情会变得更加丑陋,但这超出了这个问题的范围。
答案是:视情况而定。
C 标准描述了一个抽象机,其中优化问题无关紧要。除了访问 volatile
对象之外,如果不改变程序的可观察行为,实现可以自由地重新排序语句。 C11 为程序的
可观察行为提出了定义: (C11, 5.1.2.3p6) “符合实施的最低要求是:
— 对易失性对象的访问严格按照抽象规则进行评估 机器。
— 程序终止时,写入文件的所有数据应与程序的结果相同 根据抽象语义执行程序就会产生。
— 交互设备的输入和输出动态应按照中的规定进行 7.21.3。这些要求的目的是无缓冲或行缓冲输出 尽快出现,以确保提示消息实际出现在 等待输入的程序。
这是程序可观察到的行为”
C11也有这段(已经存在于C99中):
(C11, 5.1.2.3p10)“或者,实现可能会在每个翻译单元内执行各种优化,例如 仅当跨翻译单元边界进行函数调用时,实际语义才会与抽象语义一致。 [...]”
因此,在您的示例中,它实际上取决于
y
的声明方式(即它的链接是什么?)以及它在程序中的使用方式。
在现实生活中,编译器实际上会使用重新排序来优化代码的某些部分(如果它认为它不会影响程序的可观察行为)。