【发布时间】:2022-01-19 15:10:09
【问题描述】:
在撰写有关编译器必须如何处理 volatile 的答案时,我相信我可能偶然发现了一个 gcc 错误,并希望有人在我报告它之前进行验证。
我写了一个这样的简单函数:
int foo (int a, int b, int c)
{
b = a + 1;
c = b + 1;
a = c + 1;
return a;
}
如果不进行优化,这会导致大量无意义的数据来回移动。通过优化,编译器只需获取存储a 的寄存器,然后添加 3 并返回该结果。说 x86 lea eax, [rdi+3] 和 ret。这是意料之中的,到目前为止一切顺利。
为了演示顺序和易失性访问,我将示例更改为:
int foo (int a, int b, int c)
{
b = a + 1;
c = *(volatile int*)&b + 1;
a = c + 1;
return a;
}
这里有一个对 b 内容的左值访问,它是 volatile 限定的,据我所知,绝对不允许编译器优化该访问1)。从 gcc 4.1.2(可能更早)到 gcc 10.3,我得到了一致的行为(在 clang 中也是如此)。即使使用-O3,x86 机器代码也看起来像这样:
foo:
add edi, 1
mov DWORD PTR [rsp-4], edi
mov eax, DWORD PTR [rsp-4]
add eax, 2
ret
然后我在 gcc 11.1 及更高版本上尝试相同,现在我得到:
foo:
lea eax, [rdi+3]
ret
https://godbolt.org/z/e5x74z3Kb
ARM gcc 11.1 做了类似的事情。
这是编译器错误吗?
1) 参考:ISO/IEC 9899:2018 5.1.2.3,特别是 §2、§4 和 §6。
【问题讨论】:
-
我认为指针被认为是易失性的,它的值不会被优化,但它指向的内存不是。
-
@sorush-r 这并不重要。我告诉编译器“你必须从这里的内存中读取这个变量”,但事实并非如此。假设我有一些原因,例如虚拟读取堆上的变量以确保在我第一次使用它时现在而不是稍后执行堆分配。易失性访问副作用可以通过多种方式影响程序。
-
@sorush-r:不,它是指向
volatile int的指针。您所描述的将是*(int *volatile)&b,并且确实可以优化访问,即使使用没有此问题中描述的错误(?)的旧 GCC(如 9.4)。 godbolt.org/z/bs31xveYK(转换的 volatile 限定的指针对象结果永远不会在任何地方实现,这很好,因为它只是一个右值) -
@sorush-r
volatile int*是一个指向易失数据的指针。 -
看起来像编译器错误,类似于this。在这两种情况下,编译器都可以随意假设自动变量不能是“易失的”(这是完全正确的,除了调试程序的情况,其中变量可以在运行时更改)。
标签: c gcc compiler-optimization volatile