挥发性物质未按预期工作
Volatile not working as expected
考虑以下代码:
struct A{
volatile int x;
A() : x(12){
}
};
A foo(){
A ret;
//Do stuff
return ret;
}
int main()
{
A a;
a.x = 13;
a = foo();
}
使用g++ -std=c++14 -pedantic -O3
我得到这个程序集:
foo():
movl $12, %eax
ret
main:
xorl %eax, %eax
ret
根据我的估计,变量x
应该至少被写入三次(可能是四次),但它甚至没有写入一次(函数foo甚至没有被调用!)
更糟糕的是,当您将inline
关键字添加到foo
时,结果是:
main:
xorl %eax, %eax
ret
我认为volatile意味着即使编译器看不到读/写的意义,每一次读或写都必须发生。
这是怎么回事?
更新:
将A a;
的声明放在main之外,如下所示:
A a;
int main()
{
a.x = 13;
a = foo();
}
生成以下代码:
foo():
movl $12, %eax
ret
main:
movl $13, a(%rip)
xorl %eax, %eax
movl $12, a(%rip)
ret
movl $12, a(%rip)
ret
a:
.zero 4
这更接近你的预期。。。。我甚至比以往任何时候都更困惑
Visual C++2015没有优化分配:
A a;
mov dword ptr [rsp+8],0Ch <-- write 1
a.x = 13;
mov dword ptr [a],0Dh <-- write2
a = foo();
mov dword ptr [a],0Ch <-- write3
mov eax,dword ptr [rsp+8]
mov dword ptr [rsp+8],eax
mov eax,dword ptr [rsp+8]
mov dword ptr [rsp+8],eax
}
xor eax,eax
ret
/O2(最大化速度)和/Ox(完全优化)也是如此。
使用-O2和-O3 的gcc 3.4.4也保留了易失性写入
_main:
pushl %ebp
movl $16, %eax
movl %esp, %ebp
subl $8, %esp
andl $-16, %esp
call __alloca
call ___main
movl $12, -4(%ebp) <-- write1
xorl %eax, %eax
movl $13, -4(%ebp) <-- write2
movl $12, -8(%ebp) <-- write3
leave
ret
使用这两个编译器,如果删除volatile关键字,main()基本上变为空。
我想说,在这种情况下,编译器过于同意(错误地IMHO)决定,由于没有使用"a",因此对其进行操作是不必要的,并忽略了volatile成员。让"a"本身变得不稳定可以得到你想要的,但由于我没有一个编译器来复制它,我不能肯定。
最后(当然这是微软特有的),https://msdn.microsoft.com/en-us/library/12a04hfd.aspx说:
如果一个结构成员被标记为volatile,那么volatile将传播到整个结构。
这也指向你所看到的行为是一个编译器问题。
最后,如果您将"a"设为全局变量,编译器不太愿意认为它未使用并将其删除,这在一定程度上是可以理解的。默认情况下,全局变量是外部变量,因此不可能仅通过查看主函数就说全局"a"未使用。其他一些编译单元(.cpp文件)可能正在使用它。
GCC关于Volatile访问的页面深入了解了它的工作原理:
该标准鼓励编译器避免对易失性对象的访问进行优化,但将其实现定义为易失性访问的组成部分。最低要求是,在一个序列点上,所有先前对易失性对象的访问都已稳定,并且没有发生后续访问。因此,实现可以自由地对发生在序列点之间的易失性访问进行重新排序和组合,但对于跨序列点的访问则不能这样做。volatile的使用不允许违反在两个序列点之间多次更新对象的限制。
在C标准中:
§5.1.2.3
2访问易失性对象、修改对象、修改文件,或者调用执行任何这些操作的函数都是侧效应,11),它们是执行环境。评估一个表达可能会产生副作用效果。在调用的执行序列中的某些指定点序列点,之前评估的所有副作用都应完整,后续评估的副作用都不应存在发生。(序列点摘要见附件C。)
3在抽象机器中,所有表达式都按照指定进行求值通过语义。实际实施不需要评估一个表达式,如果它可以推断出它的值没有被使用并且没有产生所需的副作用(包括调用函数或访问易失性对象)。
[…]
5对一致性实施的最低要求是:
- 在序列点上,易失性对象是稳定的,因为之前的访问已经完成,而后续的访问尚未完成发生[…]
我选择C标准是因为语言更简单,但C++中的规则基本相同。请参阅"好像"规则
现在在我的机器上,-O1
并没有优化对foo()
的调用,所以让我们使用-fdump-tree-optimized
来看看区别:
-O1
*[definition to foo() omitted]*
;; Function int main() (main, funcdef_no=4, decl_uid=2131, cgraph_uid=4, symbol_order=4) (executed once)
int main() ()
{
struct A a;
<bb 2>:
a.x ={v} 12;
a.x ={v} 13;
a = foo ();
a ={v} {CLOBBER};
return 0;
}
和-O3
:
*[definition to foo() omitted]*
;; Function int main() (main, funcdef_no=4, decl_uid=2131, cgraph_uid=4, symbol_order=4) (executed once)
int main() ()
{
struct A ret;
struct A a;
<bb 2>:
a.x ={v} 12;
a.x ={v} 13;
ret.x ={v} 12;
ret ={v} {CLOBBER};
a ={v} {CLOBBER};
return 0;
}
gdb
表明,在这两种情况下,a
最终都被优化了,但我们担心foo()
。转储向我们表明,GCC对访问进行了重新排序,因此foo()
甚至是不必要的,随后main()
中的所有代码都被优化掉了。这真的是真的吗?让我们看看-O1
:的汇编输出
foo():
mov eax, 12
ret
main:
call foo()
mov eax, 0
ret
这基本上证实了我上面所说的。一切都经过了优化:唯一的区别是对foo()
的调用是否也一样。
- 标准::时间::d类型的挥发性对象
- 什么__asm挥发性("pause" ::: "memory");男孩
- Clang vs GCC:挥发性访问的不同代码
- C 基准测试,挥发性
- 为什么挥发性不是sig_atomic_t的一部分
- 挥发性成员变量
- 自C 11以来,挥发性在并发编程中的有用性
- C++引用挥发性对象 - 原因和影响
- 错误:“命令” /重复使用ASM挥发性之前的预期字符串字母
- 在将对象声明为挥发性时,如何在C 中超载运算符
- 关于挥发性和指标点缀
- 保持嵌套类型的挥发性
- C 挥发性的合法用例
- 为什么挥发性不使用std :: min编译
- 如何初始化具有非易失性结构的挥发性结构
- 挥发性是否充当优化的编译屏障
- 是一种基本类型的挥发性初始化,可观察到的行为
- 挥发性如何与const合作
- 拒绝挥发性RVALUE到STD :: implotial rvalue的分配是错误的
- 挥发性物质未按预期工作