优化存储/构建易失性堆栈变量是否合法?

时间:2017-10-28 21:41:51

标签: c++ optimization x86 language-lawyer volatile

我注意到clang和gcc在某些情况下优化了堆栈上声明的volatile struct的构造或赋值。例如,以下代码:

struct nonvol2 {
    uint32_t a, b;
};

void volatile_struct2()
{
    volatile nonvol2 temp = {1, 2};
}

Compiles对clang:

volatile_struct2(): # @volatile_struct2()
  ret

另一方面,gcc不会删除商店,虽然它确实将两个隐含商店优化为一个商店:

volatile_struct2():
        movabs  rax, 8589934593
        mov     QWORD PTR [rsp-8], rax
        ret

奇怪的是,clang不会将易失性存储优化为单个int变量:

void volatile_int() {
    volatile int x = 42;
}

编译为:

volatile_int(): # @volatile_int()
  mov dword ptr [rsp - 4], 1
  ret

此外,没有优化具有1个成员而不是2个成员的结构。

虽然gcc在这种特殊情况下不会删除构造,但在struct成员本身被声明为volatile而不是{{}的情况下,它可能会进行更积极的优化。 1}}本身在构造点:

struct

简单地编译为简单的typedef struct { volatile uint32_t a, b; } vol2; void volatile_def2() { vol2 temp = {1, 2}; vol2 temp2 = {1, 2}; temp.a = temp2.a; temp.a = temp2.a; }

虽然它看起来完全合理但是#34;要删除这些通过任何合理的过程几乎无法观察到的商店,我的印象是在标准的ret加载和存储被认为是程序的可观察行为的一部分(除了调用IO函数),完全停止。这意味着它们不会被"删除,因为它会根据定义更改程序的可观察行为

我错了,还是在这里违反规定?也许构造被排除在必须假设volatile有副作用的情况下?

2 个答案:

答案 0 :(得分:4)

让我们来研究标准直接说的内容。 volatile的行为由一对语句定义。 [intro.execution] / 7:

  

符合实施的最低要求是:

     
      
  • 严格按照抽象机的规则评估通过volatile glvalues的访问。
  •   
     

...

和[intro.execution] / 14:

  

读取由volatile glvalue(6.10)指定的对象,修改对象,调用库I / O函数或调用执行任何这些操作的函数都是副作用,这些都是状态的变化。执行环境。

嗯,[intro.execution] / 14不适用,因为上述代码中没有任何内容构成"读取对象"。你初始化并销毁它;它永远不会被阅读。

所以离开[intro.execution] / 7。这里重要的短语是"访问 volatile glvalues"。虽然temp肯定是volatile值,但它肯定是一个glvalue ......你实际上从未实际访问过它。哦,是的,你初始化了对象,但实际上并没有访问#34;尽管" temp作为glvalue。

也就是说,根据glvalue的定义,temp作为表达式是glvalue:"一种表达式,其评估决定了对象,位域或函数的身份。"语句在glvalue中创建和初始化temp 结果,但temp的初始化不能通过glvalue访问。

volatile视为const。关于const个对象的规则在之后初始化之前不适用。同样,关于volatile个对象的规则要么在初始化之后才适用。

因此volatile nonvol2 temp = {1, 2};volatile nonvol2 temp; temp.a = 1; temp.b = 2;之间存在差异。而Clang当然does the right thing in that case

话虽如此,Clang在这种行为方面的不一致性(仅在使用结构时优化它,并且仅在使用包含多个成员的结构时)才表明这可能不是正式的优化Clang的作家。也就是说,他们并没有充分利用措辞,因为这只是一些意外代码混合在一起的奇怪怪癖。

  

虽然gcc在这种特殊情况下不会删除构造,但在struct成员本身被声明为volatile而不是{{}的情况下,它可能会进行更积极的优化。 1}}本身在构造点:

海湾合作委员会的行为是:

  1. 不符合标准,因为它违反[intro.execution] / 7,但
  2. 绝对没有办法证明它不符合标准。
  3. 鉴于您编写的代码,用户根本无法检测这些读取和写入是否实际发生。我宁愿怀疑你做任何事情让外界看到它的那一刻,这些变化会突然出现在编译的代码中。无论标准多么称之为"可观察的行为",事实是,通过C ++自己的内存模型,没有人可以看到它

    海湾合作委员会由于缺乏证人而逃脱了犯罪。或者至少是可靠的证人(任何能够看到它的人都会犯UB)。

    因此,您不应将struct视为某些优化取消开关。

答案 1 :(得分:3)

从标准的角度来看,没有要求实现记录任何对象如何物理存储在内存中。即使实现记录了使用类型unsigned char*的指针访问某种类型的对象的行为,也允许实现以其他方式物理存储数据,然后调整基于字符的读取和写入的代码适当的行为。

如果执行平台指定了抽象机器对象与CPU看到的存储之间的关系,并定义了对某些CPU地址的访问可能触发编译器不知道的副作用的方式,那么适合低的质量编译器该平台上的级别编程应生成代码,其中volatile - 限定对象的行为与该规范一致。标准并未试图强制要求所有实现都适用于低级编程(或任何其他特定目的)。

如果自动变量的地址永远不会暴露给外部代码,volatile限定符只需要有两个效果:

  1. 如果在函数内调用setjmp,编译器必须执行必要的操作以确保longjmp不会破坏任何volatile - 限定对象的值,甚至如果它们是在setjmplongjmp之间写的。如果没有限定符,setjmplongjmp之间写入的对象的值在执行longjmp时将变得不确定。

  2. 允许编译器假定任何没有副作用的循环将运行完成的规则​​不适用于在循环内访问volatile对象的情况,无论实现是否定义任何可以观察到这种访问的方式。

  3. 除非在这些情况下,as-if规则允许编译器以与物理机无关的方式在抽象机中实现volatile限定符。