对于{A = a; B = B; },在“B = b”之前会严格执行“A = a”吗?

假设ABab都是variables, ABab的地址都是不同的。 然后,对于下面的代码:

 A = a; B = b; 

C和C ++标准明确要求A=aB=b之前严格执行吗? 鉴于ABab的地址都不相同,编译器是否允许为了某种目的(如优化)交换两个语句的执行顺序?

如果我的问题的答案在C和C ++中是不同的,我想知道两者。

编辑:问题的背景是以下。 在棋盘游戏AIdevise中,为了优化,人们使用无锁共享哈希表 ,如果我们不添加volatile限制,其正确性强烈依赖于执行顺序。

这两个标准都允许这些指令无序执行,只要这不会改变可观察到的行为。 这被称为as-if规则:

请注意,正如在注释中指出的那样,“可观察行为”是指具有定义行为的程序的可观察行为。 如果你的程序有未定义的行为,那么编译器可以免于推理。

编译器只有义务模拟程序的可观察行为,所以如果重新sorting不会违反这个原则,那么它将被允许。 假设行为定义良好,如果程序包含未定义的行为 ,如数据竞争,那么程序的行为将是不可预知的,并且作为注释需要使用某种forms的同步来保护关键部分。

一个有用的参考

一个有趣的文章,涵盖这是编译时的内存sorting,它说:

编译器开发人员和CPU供应商普遍遵循的内存重新sorting的基本规则可以表述如下:

你不应该修改单线程程序的行为。

一个例子

文章提供了一个简单的程序,我们可以看到这个重新sorting:

 int A, B; // Note: static storage duration so initialized to zero void foo() { A = B + 1; B = 0; } 

并且在更高的优化级别B = 0显示在A = B + 1 ,我们可以使用Godbolt来重现这个结果,而使用-O3产生下面的结果( 见它 ):

 movl $0, B(%rip) #, B addl $1, %eax #, D.1624 

为什么?

为什么编译器重新sorting? 文章解释说,处理器完全是这样做的原因,因为架构的复杂性:

正如我在开始时提到的那样,编译器修改内存交互顺序的原因与处理器相同 – 性能优化。 这种优化是现代CPU复杂性的直接后果。

标准

在C ++标准草案中,第1.9节介绍了程序的执行情况强调我的前进 ):

本标准中的语义描述定义了一个参数化的非确定性抽象机器。 本国际标准对合规实施的结构没有要求。 特别是,他们不需要复制或模拟抽象机器的结构。 相反,需要符合的实现来模拟(仅)抽象机器的可观察行为,如下所述。

脚注5告诉我们这也被称为as-if规则

这个规定有时被称为“假设”规则 ,因为一个实现可以自由地忽视这个国际标准的任何要求只要结果就好像该要求已经被遵守一样,就可以从可观察到的行为的程序。 例如,一个实际的实现不需要评估一个expression式的一部分,如果它能够推断出它的值没有被使用,并且没有产生影响该程序的可观察行为的副作用。

C99草案和C11标准草案在5.1.2.3节的执行中涵盖了这一点,尽pipe我们必须去索引中看到它也被称为C标准中的as-if规则

如果规则5.1.2.3

更新无locking注意事项

“ locking自由编程入门 ”一文很好地涵盖了这个主题,对于OP关注无锁共享哈希表的实现,本节可能是最相关的:

内存sorting

如stream程图所示,无论您对多核(或任何对称多处理器 )进行无锁编程,且您的环境不保证顺序一致性,您必须考虑如何防止内存重新sorting 。

在当今的体系结构中,执行正确的内存sorting的工具通常分为三类,它们可以防止编译器重新sorting和处理器重新sorting :

  • 一个轻量级的同步或篱笆指令,我将在以后的post中讨论 ;
  • 一个完整的内存围栏指令,我以前已经演示过 ;
  • 提供获取或释放语义的内存操作。

获取语​​义防止按照程序顺序对其进行操作的内存重新sorting,并且释放语义可以防止在其之前的操作的内存重新sorting。 这些语义特别适用于有生产者/消费者关系的情况,一个线程发布一些信息,另一个线程读取它。 我将在未来的post中进一步讨论这个问题。

如果不存在指令依赖性,那么如果最终结果不受影响,则这些指令也可能被无序执行。 在debugging以更高优化级别编译的代码时,您可以观察到这一点。

由于A = a; 和B = b; 在数据依赖方面是独立的,这应该不重要。 如果前面指令的输出/结果影响后续指令的input,则sorting,否则不排除。 这通常是严格顺序执行的。

我的阅读是,这是需要工作的C + +标准; 然而,如果你正在尝试使用这种方法进行multithreading控制,那么在这种情况下它不起作用,因为这里没有任何东西可以保证寄存器以正确的顺序写入内存。

正如你的编辑所表明的那样,你正试图在无法使用的地方使用它。

如果你这样做可能会感兴趣:

 { A=a, B=b; /*etc*/ } 

注意逗号代替分号。

然后,C ++规范和任何确认编译器将不得不保证执行顺序,因为逗号运算符的操作数总是从左到右进行计算。 这确实可以用来防止优化器通过重新sorting来颠覆线程同步。 逗号有效地成为不允许重新sorting的障碍。