struct s { int dummy[1]; }; volatile struct s s; int main(void) { s; return 0; }
和
struct s { int dummy[16]; }; volatile struct s s; int main(void) { s; return 0; }
以下是我使用gcc 4.6.2为他们:
_main: pushl %ebp movl %esp,%ebp andl $-16,%esp call ___main movl _s,%eax xorl %eax,%eax leave ret .comm _s,4,2
和
_main: pushl %ebp movl %esp,%esp call ___main xorl %eax,64,5
请注意在第二种情况下无法访问s.
是否是编译器错误,或者我只是处理C标准的以下语句,gcc开发人员只是选择了这样一个奇怪的实现定义,并且仍然遵循规则:
What constitutes an access to an object that has volatile-qualified type is implementation-defined.
这个差异的原因是什么?我自然会期望整个结构被访问(或不被访问,我不知道),不管其大小和内部的内容.
附:在这种情况下,您的编译器(非gcc或更新的gcc)是做什么的? (如果这是您要解决的唯一部分,请在评论中回答这个最后一个问题,因为这不是主要问题,更多是好奇的问题).
解决方法
铛 – 3.4
当将这些片段中的任一个编译为C时,发出的程序集在任何情况下都不引用.实际上发出了两个警告:
volatile.c:8:2:warning:表达式结果未使用;分配给一个变量来强制易变负载[-Wunused-volatile-lvalue]
S;
在C99模式下编译时,不会发出这些警告.如this blog post和this GCC wiki entry from the question comments中所述,在这种情况下使用s会导致C中的lvalue-to-rvalue转换,而不是C中的.这通过检查C的Clang AST来确认,因为来自LvalueToRValue的ImplicitCastExpr在C中生成的AST中不存在. (AST不受struct的大小的影响).
Clang源的快速grep揭示了在聚合表达式的排放中:
case CK_LValueToRValue: // If we're loading from a volatile type,force the destination // into existence. if (E->getSubExpr()->getType().isVolatileQualified()) { EnsureDest(E->getType()); return Visit(E->getSubExpr()); }
EnsureDest强制排放堆栈槽,大小和类型为表达式.由于优化器不允许删除易失性访问,它们分别保留在IR和输出asm中的标量加载/存储和memcpy.鉴于上述情况,这是我期望的行为.
GCC-4.8.2
在这里,我观察到与问题相同的行为.但是当我改变表达式时,到s.dummy;访问不会出现在任一版本中.我不熟悉gcc的内部,因为我使用LLVM,所以我不能猜测为什么会发生这种情况.但是基于上述观察结果,我会说这是一个由于不一致而导致的编译器错误.