对于{A = a; B = B; },在“B = b”之前会严格执行“A = a”吗?
假设A
, B
, a
和b
都是variables, A
, B
, a
和b
的地址都是不同的。 然后,对于下面的代码:
A = a; B = b;
C和C ++标准明确要求A=a
在B=b
之前严格执行吗? 鉴于A
, B
, a
和b
的地址都不相同,编译器是否允许为了某种目的(如优化)交换两个语句的执行顺序?
如果我的问题的答案在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的障碍。