我想创建一个始终返回零的函数,但是对于优化器来说,这一事实应该并不明显,因此,由于“已知零”状态,使用该值进行的后续计算将不会不断折叠。 / p>
在没有链接时优化的情况下,这通常就像将其放入自己的编译单元一样简单:
int zero() {
return 0;
}
优化器看不到单位,因此不会发现该函数的始终为零的性质。
但是,我需要与LTO一起使用的东西,以及将来尽可能多的聪明优化方法。我考虑过从全球阅读:
int x;
int zero() {
return x;
}
...但是在我看来,足够聪明的编译器会注意到x
从未被写入,而仍然确定zero()
始终为零。
我考虑使用volatile
,例如:
int zero() {
volatile int x = 0;
return x;
}
...但是可变读取所需副作用的实际语义尚不清楚,并且似乎不能排除函数仍返回零的可能性。
这样的始终为零但不是在编译时的值在几种情况下很有用,例如在两个值之间强制使用no-op依赖性。像这样的东西:a += b & zero()
导致a
依赖于最终二进制文件中的b
,但不会更改a
的值。
不要通过告诉我“标准不能保证采取任何方式来做到这一点”来回答这个问题-我很清楚,我正在寻找实用的答案,而不是标准的语言。
答案 0 :(得分:2)
如果编译器能够解决这个问题,我会感到惊讶:
int not_a_zero_honest_guv()
{
// static makes sure the initialization code only gets called once
static int const i = std::ifstream("") ? 1:0;
return i;
}
int main()
{
std::cout << not_a_zero_honest_guv();
}
这使用函数本地静态函数的复杂(不可预测)运行时初始化。如果顽皮的小编译器发现空文件名总是会失败,请在其中放置一些非法文件名。
答案 1 :(得分:2)
首先,我认为OP的第三项建议:
int zero() {
volatile int x = 0;
return x;
}
实际上可以工作(但这不是我的答案;请参见下文)。 Is it allowed for a compiler to optimize away a local volatile variable?的主题是两周前完全相同的功能,经过很多讨论和不同意见,在此不再赘述。但有关此方面的最新测试,请参见https://godbolt.org/g/SA7k5P。
我的答案是在上面添加一个static
,即:
int zero() {
static volatile int x;
return x;
}
在此处查看一些测试:https://godbolt.org/g/qzWYJt。
现在,通过添加static
,“可观察到的行为”的抽象概念变得更加可信。通过一点点工作,我可以弄清楚x
的地址,尤其是在禁用Address space layout randomization的情况下。这可能在.bss
段中。然后,再进行一些工作,我可以将调试器/黑客工具附加到正在运行的进程中,然后更改 x
的值。使用volatile
,我已经告诉编译器我可以这样做,因此不允许通过优化x
来更改这种“可观察的行为”。 (它也许可以通过内联将 call 优化为zero
,但我不在乎。)
Is it allowed for a compiler to optimize away a local volatile variable?的标题有点误导,因为讨论的焦点是x
在 stack 上,而不是 local变量。因此,此处不适用。但是我们可以将x
从本地范围更改为文件范围,甚至更改为全局范围,如下所示:
volatile int x;
int zero() {
return x;
}
这不会改变我的观点。
进一步的讨论:
是的,volatile
有时会出现问题:例如,请参见https://godbolt.org/g/s6JhpL和Does accessing a declared non-volatile object through a volatile reference/pointer confer volatile rules upon said accesses?中显示的指向易失性指针。
是的,有时(总是?)编译器有错误。
但是我想证明这种解决方案不是一个极端的情况,并且编译器作者之间已经达成共识,我将通过查看现有分析来做到这一点。
John Regehr的2010年博客文章Volatile Structs Are Broken报告了一个错误,该错误在gcc和Clang中都对易失性访问进行了优化。 (它在三个小时内固定。)一位评论员引用了该标准(强调了一点):
“ 6.7.3 ...对具有volatile限定类型的对象的访问是实现定义的。”
Regehr同意,但是补充说,在非边缘案件中如何使用它已经达成共识:
是的,定义了对易失性变量的访问权限。但是您错过了以下事实:所有合理的C实现都将对volatile变量的读取视为读取访问,将对volatile变量的写入视为写入访问。
有关更多参考。看到:
另一个Regehr 2010博客文章Nine ways to break your systems code using volatile。
Wintermute的answer至Volatile and its harmful implications。
这些是有关编译器错误和程序员错误的报告。但是它们显示了volatile
应该/如何工作,并且这个答案符合那些准则。
答案 2 :(得分:0)
您会发现每个编译器都具有实现此目的的扩展。
海湾合作委员会:
__attribute__((noinline))
int zero()
{
return 0;
}
MSVC:
__declspec(noinline)
int zero()
{
return 0;
}
答案 3 :(得分:0)
在clang和gcc上,破坏变量是可行的,但会带来一些开销
int zero()
{
int i = 0;
asm volatile(""::"g"(&i):"memory");
return i;
}
在gcc的O3下被编译为
mov DWORD PTR [rsp-4], 0
lea rax, [rsp-4]
mov eax, DWORD PTR [rsp-4]
ret
和叮当声
mov dword ptr [rsp - 12], 0
lea rax, [rsp - 12]
mov qword ptr [rsp - 8], rax
mov eax, dword ptr [rsp - 12]
ret
Live。