5

下面的代码示例来自一个中文博客,介绍了volatile. 左边是C代码;另一个是生成的汇编代码。

// cordering.c                                gcc -O2 -S -masm=intel cordering.c

int A;
volatile int B;
void foo()                                    mov   eax, DWORD PTR B[rip]
{                                             mov   DWORD PTR B[rip], 0
    A = B + 1;                                add   eax, 1
    B = 0;                                    mov   DWORD PTR A[rip], eax
}                                             ret

正如我们在汇编代码中看到的, 的副作用A放在 的 副作用之后B,即使Bvolatile合格的。但是,cppreference.com 说

[W]在单个执行线程中,不能优化或重新排序易失性访问,并使用另一个可见的副作用,即先排序或先排序后易失性访问

这里的副作用A是在之前排序的B,所以我认为编译器这样做是非法的。我对吗?


作为补充,博客说如果我们要保证 avolatilenon-volatiletype 之间的顺序,我们需要同时做volatile

// cordering.c                                gcc -O2 -S -masm=intel cordering.c

volatile int A;
volatile int B;
void foo()                                    mov   eax, DWORD PTR B[rip]
{                                             add   eax, 1
    A = B + 1;                                mov   DWORD PTR A[rip], eax
    B = 0;                                    mov   DWORD PTR B[rip], 0
}                                             ret
4

1 回答 1

5

您链接的页面说:

通过 volatile 限定类型的 glvalue 表达式进行的每次访问(读取或写入操作、成员函数调用等)都被视为出于优化目的的可见副作用 (即,在单个执行线程中,volatile访问不能被优化或重新排序,另一个可见的副作用是在易失性访问之前或之后排序。

因此,如果Ais not volatile,访问A不会被视为可见的副作用,并且第二个语句不适用(因为它没有说明重新排序可见和不可见访问的任何内容)。

编辑:请注意,cppreference 不是 C++ 的官方文档,而是社区的努力。我确实相信粗体声明的目的是定义什么是可见的副作用,但没有写清楚。

最终参考是 C++ 标准(这里有一些获取它的选项)。N4659标准草案在[intro.execution]第 14 段中定义了副作用,并通过[ intro.races]中的 2 页定义链定义了可见的副作用。我不是 C++ 标准方面的专家,因此如果不付出很大的努力,我无法解读标准的确切含义,但欢迎您试一试。

但是,对于允许编译器进行哪些优化的非正式解释,您可以查看 cppreference 上的as-if 规则

编辑 2:该标准还在 [intro.execution] 第 7 段中正式指定了as - if规则:

对一致性实现的最低要求是:
(7.1) — 通过 volatile glvalues 的访问严格按照抽象机的规则进行评估。
(7.2) — 在程序终止时,写入文件的所有数据应与根据抽象语义执行程序可能产生的结果之一相同。
(7.3) — 交互式设备的输入和输出动态应该以这样一种方式发生,即在程序等待输入之前实际交付提示输出。构成交互式设备的内容是实现定义的。

简而言之,任何优化都是有效的,只要程序产生相同的输出,并且对 volatile 对象的读取和写入以正确的顺序发生,这适用于您的原始示例。

于 2018-04-14T08:18:03.557 回答