我注意到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
有副作用的情况下?
答案 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}}本身在构造点:
海湾合作委员会的行为是:
鉴于您编写的代码,用户根本无法检测这些读取和写入是否实际发生。我宁愿怀疑你做任何事情让外界看到它的那一刻,这些变化会突然出现在编译的代码中。无论标准多么称之为"可观察的行为",事实是,通过C ++自己的内存模型,没有人可以看到它。
海湾合作委员会由于缺乏证人而逃脱了犯罪。或者至少是可靠的证人(任何能够看到它的人都会犯UB)。因此,您不应将struct
视为某些优化取消开关。
答案 1 :(得分:3)
从标准的角度来看,没有要求实现记录任何对象如何物理存储在内存中。即使实现记录了使用类型unsigned char*
的指针访问某种类型的对象的行为,也允许实现以其他方式物理存储数据,然后调整基于字符的读取和写入的代码适当的行为。
如果执行平台指定了抽象机器对象与CPU看到的存储之间的关系,并定义了对某些CPU地址的访问可能触发编译器不知道的副作用的方式,那么适合低的质量编译器该平台上的级别编程应生成代码,其中volatile
- 限定对象的行为与该规范一致。标准并未试图强制要求所有实现都适用于低级编程(或任何其他特定目的)。
如果自动变量的地址永远不会暴露给外部代码,volatile
限定符只需要有两个效果:
如果在函数内调用setjmp
,编译器必须执行必要的操作以确保longjmp
不会破坏任何volatile
- 限定对象的值,甚至如果它们是在setjmp
和longjmp
之间写的。如果没有限定符,setjmp
和longjmp
之间写入的对象的值在执行longjmp
时将变得不确定。
允许编译器假定任何没有副作用的循环将运行完成的规则不适用于在循环内访问volatile对象的情况,无论实现是否定义任何可以观察到这种访问的方式。
除非在这些情况下,as-if规则允许编译器以与物理机无关的方式在抽象机中实现volatile
限定符。