【发布时间】:2018-04-10 05:21:39
【问题描述】:
我注意到,在某些情况下,clang 和 gcc 优化了栈上声明的 volatile struct 的构造或赋值。例如以下代码:
struct nonvol2 {
uint32_t a, b;
};
void volatile_struct2()
{
volatile nonvol2 temp = {1, 2};
}
Compiles 铿锵到:
volatile_struct2(): # @volatile_struct2()
ret
另一方面,gcc 不会删除存储,尽管它确实将两个隐含存储优化为一个:
volatile_struct2():
movabs rax, 8589934593
mov QWORD PTR [rsp-8], rax
ret
奇怪的是,clang 不会将 volatile 存储优化为单个 int 变量:
void volatile_int() {
volatile int x = 42;
}
编译为:
volatile_int(): # @volatile_int()
mov dword ptr [rsp - 4], 1
ret
此外,具有 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;
}
简单地编译成一个简单的ret。
虽然删除这些几乎不可能通过任何合理过程观察到的存储似乎完全“合理”,但我的印象是,在标准 volatile 中,加载和存储被假定为 observable 的一部分程序的行为(除了对 IO 函数的调用),句号。这意味着它们不会被“好像”删除,因为根据定义它会改变程序的可观察行为。
这是我错了,还是 clang 违反了这里的规则?也许 construction 被排除在必须假定volatile 具有副作用的情况之外?
【问题讨论】:
-
这使用 Apple LLVM 版本 8.1.0 (clang-802.0.42) 在 macOS 10.12.6 上针对 x86_64 进行复制,使用“-O3”编译。我必须在源代码中插入
#include <stdint.h>。使用二元素结构,写入被优化掉。对于单元素结构,它不是。 -
@EricPostpischil - 我在问题中包含了godbolt link,尽管它在那里可能不是很明显。您可以通过将参数更改为使用
--version来获取所需的信息。我得到了:clang version 5.0.0 (tags/RELEASE_500/final 312636), Target: x86_64-unknown-linux-gnu, Thread model: posix。据我所知,这种行为一直是相同的(回到 clang 3.0)。 -
它也用 C 而不是 C++ 重现。所以这不是 C++ 构造函数问题。我认为这可能是一个真正的编译器错误。 (而且,即使我们对需要 volatile 访问是错误的 [因此编译器优化二元素结构访问是正确的],编译器不优化一元素结构访问也是错误的。[错误,因为它错过了一种优化,不是因为它违反了语言规范。])
-
半相关:C11 中的
struct-assignment 不是成员,因此它忽略了成员上的_Atomic限定符(可能还有volatile)。 (在 C++11 中,atomic<T>没有复制构造函数,并且它确实按成员进行,所以它不编译。)godbolt.org/g/heUd1R。我正在考虑就这个很少记录的事实提出一个问题,但我想我通过查看标准回答了我自己的 C11 问题,gcc 可能应该发出警告,但很明显,只使用 memcpy 或rep stosq是正确的行为(从零结构复制)。 -
C 或 C++ 中的结构赋值或初始化不能解释为什么 gcc 对成员访问的易失性成员进行优化。 (你的最后一个例子。为 gcc 和 clang godbolt.org/g/1oGFq4 添加到 Godbolt(C11 和 C++11,尽管事实证明移植到 C11 不会改变任何编译器的 asm 输出)。)
标签: c++ optimization x86 language-lawyer volatile