GCC 可能执行的重新排序与 (x86) CPU 可能执行的重新排序无关。
让我们从编译器重新排序。 C语言规则是这样的:GCC禁止重排序volatile
相互加载和存储内存访问,或删除它们,当它们之间出现序列点时(谢谢bobc https://stackoverflow.com/a/32536664/2809095对此进行澄清)。也就是说,在汇编输出中,这些内存访问将出现,并且将按照您指定的顺序精确排序。非-volatile
另一方面,访问可以相对于所有其他访问重新排序,volatile
或不,前提是(根据假设规则)计算的最终结果相同。
例如,一个非volatile
C 代码中的加载可以按照代码所述多次执行,但顺序不同(例如,如果编译器认为在有更多寄存器可用时提前或稍后执行更方便)。它可以比代码所说的次数更少(例如,如果值的副本碰巧在大型表达式中间的寄存器中仍然可用)。或者它甚至可以被删除(例如,如果编译器可以证明加载是无用的,或者如果它将变量完全移动到寄存器中)。
为了防止编译器在其他时候重新排序,您必须使用特定于编译器的屏障。海湾合作委员会使用__asm__ __volatile__("":::"memory");
以此目的。
这不同于CPU重新排序,又名内存排序模型。古代 CPU 严格按照指令在程序中出现的顺序执行指令;这就是所谓的节目排序, 或者强记忆排序模型。然而,现代 CPU 有时会通过“作弊”来提高运行速度削弱一点内存模型。
x86 CPU 削弱内存模型的方式记录在 Intel 软件开发人员手册,第 3 卷,第 8 章,第 8.2.2 节中“P6 及更新的处理器系列中的内存排序”。其部分内容如下:
- 读取不会与其他读取重新排序。
- 写入不会与较旧的读取重新排序。
- 对内存的写入不会与其他写入重新排序,但[某些]例外。
- 读取可以与对不同位置的较旧写入进行重新排序,但不能与对同一位置的较旧写入进行重新排序。
- 读取或写入不能使用 I/O 指令、锁定指令或序列化指令重新排序。
- 读取无法通过较早的 LFENCE 和 MFENCE 指令。
- 写入无法传递较早的 LFENCE、SFENCE 和 MFENCE 指令。
- LFENCE 指令无法传递较早的读取。
- SFENCE 指令无法传递较早的写入。
- MFENCE 指令无法传递较早的读取或写入。
它还在第 8.2.3 节中提供了非常好的示例,说明哪些内容可以重新排序,哪些内容不可以重新排序“说明内存排序原则的示例”.
正如您所看到的,使用 FENCE 指令来防止 x86 CPU 不恰当地重新排序内存访问。
最后,您可能感兴趣this http://preshing.com/20120625/memory-ordering-at-compile-time/链接,其中包含更多详细信息,并附带您渴望的组装示例。