编译器可以通过指向易失性的指针优化存储吗?

Bee*_*ope 7 c++ volatile language-lawyer

写入 volatile 变量是C++中的某种副作用,通常不能在 as-if 规则下优化。在实践中,这通常意味着在检查程序集时,您会看到抽象机器1为每个易失性存储提供一个存储。

但是,我不清楚是否必须在以下情况下执行存储,其中底层对象不是 volatile,但存储是通过指向 volatile 的指针完成的:

void vtest() {
    int buf[1];

    volatile int * vptr = buf;

    *vptr = 0;
    *vptr = 1;
    *vptr = 2;
}
Run Code Online (Sandbox Code Playgroud)

在这里,gcc 实际上优化了所有的存储。叮当没有。奇怪的是,行为取决于缓冲区大小:buf[3]gcc 会发出存储,但buf[4]不会等等。

gcc在这里的行为合法吗?


[1] 有一些小的变化,例如,一些编译器将在 x86 上使用单个读-修改-写指令来实现诸如v++wherev是 volatile 之类的东西)。

sup*_*cat 1

虽然 C 和 C++ 标准能够识别易失性加载和存储具有特定语义的实现类别,并通过预定义的宏、内在函数或其他此类手段报告特定实现正在使用的语义,但这两种实现目前都没有这样做。所以。给定一个循环:

void hang_if_nonzero(int mode)
{
  int i = 1;
  do { +*(volatile int*)0x1234; } while(mode);
}
Run Code Online (Sandbox Code Playgroud)

如果模式非零,编译器将需要生成阻止程序执行的代码,因为读取volatile定义为本身的副作用,无论是否有任何方法可以实现执行它的效果与跳过它的区别。然而,不要求编译器实际生成用于易失性访问的任何加载指令。如果编译器指定它仅用于读取地址 0x1234 的效果与跳过读取的效果无法区分的硬件平台,则允许跳过读取。

如果获取了对象的地址,但编译器可以考虑使用该地址的所有方式,并且代码从不检查地址的表示形式,则编译器不需要分配“正常”可寻址存储,但可以它的闲暇分配一个寄存器或其他形式的存储,这些存储不会通过正常的加载和存储进行访问。如果它可以知道对象在访问时将包含什么值,它甚至可以假装分配存储而实际上并不这样做。例如,如果一个程序要做类似的事情:

int test(int mode)
{
  int a[2] = {1,2};
  int *p = a;
  return p[mode & 1] + p[mode & 1];
}
Run Code Online (Sandbox Code Playgroud)

编译器不需要实际为 分配任何存储空间a,但可以在闲暇时生成与 等效的代码return (1+(mode & 1)) << 1;。即使p被声明为int volatile *p = a;,编译器也不需要为 分配可寻址存储a,因为编译器仍然可以解释通过指针完成的所有事情a,因此没有义务保留a在可寻址存储中。因此,编译器可以将 的读取a[mode & 1]视为等同于计算表达式(1+(mode & 1))。如果读取是通过易失性指针完成的,则需要将其视为副作用,以确定是否可以省略循环,但不要求读取本身实际上执行任何操作。