优化商店/构建易变的堆栈变量是否合法?

Bee*_*ope 8 c++ optimization x86 volatile language-lawyer

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

struct nonvol2 {
    uint32_t a, b;
};

void volatile_struct2()
{
    volatile nonvol2 temp = {1, 2};
}
Run Code Online (Sandbox Code Playgroud)

编译 clang:

volatile_struct2(): # @volatile_struct2()
  ret
Run Code Online (Sandbox Code Playgroud)

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

volatile_struct2():
        movabs  rax, 8589934593
        mov     QWORD PTR [rsp-8], rax
        ret
Run Code Online (Sandbox Code Playgroud)

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

void volatile_int() {
    volatile int x = 42;
}
Run Code Online (Sandbox Code Playgroud)

编译为:

volatile_int(): # @volatile_int()
  mov dword ptr [rsp - 4], 1
  ret
Run Code Online (Sandbox Code Playgroud)

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

虽然gcc在这种特殊情况下不会删除构造,但在struct成员本身被声明的情况下,它可能甚至更积极的优化volatile,而不是在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;
}
Run Code Online (Sandbox Code Playgroud)

简单地编译成一个简单的ret.

虽然删除这些几乎不可能通过任何合理的过程观察到的商店似乎完全"合理",但我的印象是,在标准的volatile加载和存储中假定是程序的可观察行为的一部分(除了调用之外)到IO功能),完全停止.这意味着它们不会被"似乎"删除,因为它将根据定义改变程序的可观察行为.

我错了,还是在这里违反了规则?也许建筑被排除在volatile必须假设有副作用的情况之外?

sup*_*cat 4

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

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

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

  1. 如果setjmp在函数内调用,编译器必须采取一切必要措施以确保longjmp不会破坏任何 - 限定对象的值volatile,即使它们是在setjmp和之间编写的longjmp。如果没有限定符,则在执行a 时,在setjmp和之间写入的对象的值longjmp将变得不确定。longjmp

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

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