创建一个始终返回零但优化器不知道的函数
Create a function that always returns zero, but the optimizer doesn't know
我想创建一个总是返回零的函数,但这一事实对优化器来说不应该是显而易见的,这样使用该值的后续计算就不会因为"已知零"状态而不断减少。
在没有链接时间优化的情况下,这通常就像把它放在自己的编译单元中一样简单:
int zero() {
return 0;
}
优化器无法跨单元查看,因此不会发现此函数的始终为零的特性。
然而,我需要一些能与LTO一起工作的东西,以及未来尽可能多的巧妙优化。我考虑过从一个全球性的:
int x;
int zero() {
return x;
}
但在我看来,一个足够聪明的编译器可以注意到CCD_ 1从未被写入,并且仍然决定CCD_。
我考虑过使用volatile
,比如:
int zero() {
volatile int x = 0;
return x;
}
但是volatile读取所需副作用的实际语义并不完全清楚,而且似乎也不排除函数仍然返回零的可能性。
这样一个始终为零但不在编译时的值在某些情况下很有用,例如强制两个值之间的无操作依赖关系。类似于:a += b & zero()
导致a
依赖于最终二进制文件中的b
,但不会更改a
的值。
不要告诉我"标准并不能保证有任何方法可以做到这一点"——我很清楚,我正在寻找一个实用的答案,而不是标准中的语言。
如果编译器能解决这个问题,我会感到惊讶:
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();
}
这使用了一个复杂的(不可预测的)本地静态函数的运行时初始化。如果顽皮的小编译器发现一个空文件名总是会失败,那么就在里面放一些非法的文件名。
首先说一句:我相信OP的第三个建议:
int zero() {
volatile int x = 0;
return x;
}
事实上是可行的(但这不是我的答案;见下文)。两周前,这个完全相同的函数是《允许编译器优化掉局部易失性变量吗?》的主题?,有很多讨论和不同意见,我在此不再赘述。但要了解最近对此的测试,请参阅https://godbolt.org/g/SA7k5P.
我的答案是在上面添加一个static
,即:
int zero() {
static volatile int x;
return x;
}
请在此处查看一些测试:https://godbolt.org/g/qzWYJt.
现在,随着static
的加入,"可观察行为"的抽象概念变得更加可信。只要做一点工作,我就可以算出x
0的地址,特别是如果我禁用了地址空间布局随机化。这可能在.bss
段中。然后,再做一点工作,我就可以在运行的进程中附加一个调试器/黑客工具,然后更改x
的值。对于volatile
,我已经告诉编译器我可能会这样做,所以不允许通过优化x
来改变这种"可观察的行为"。(它也许可以通过内联来优化对zero
的调用,但我不在乎。)
标题是"允许编译器优化掉局部易失性变量吗?"?这有点误导,因为讨论集中在x
位于堆栈上,而不是局部变量。所以这里不适用。但我们可以将x
从本地范围更改为文件范围,甚至全局范围,如:
volatile int x;
int zero() {
return x;
}
这不会改变我的论点。
进一步讨论:
是的,volatile
有时会有问题:例如,请参阅此处显示的指向易失性问题的指针https://godbolt.org/g/s6JhpL以及在通过易失性引用/指针访问声明的非易失性对象是否将易失性规则赋予所述访问?。
是的,有时(总是?)编译器会有错误。
但我想说的是,这个解决方案不是一个边缘案例,编译器作者之间达成了共识,我将通过查看现有的分析来做到这一点。
John Regehr在2010年的博客文章《Volatile Structs Are Broken》中报告了一个漏洞,其中在gcc和Clang中都优化了Volatile访问。(它在三个小时内就被固定了。)一位评论员引用了标准(增加了重点):
"6.7.3…对具有volatile限定类型的对象的访问是由实现定义的。">
Regehr表示同意,但补充说,在如何处理非边缘情况方面达成了共识:
是的,对易失性变量的访问是由实现定义的。但是,您忽略了这样一个事实,即所有合理的C实现都将从易失性变量读取视为读取访问,将对易失性可变变量的写入视为写入访问。
获取更多参考。参见:
E。Eide,J.Regehr,"Volatiles Are Miscompiled,and What to Do It",第八届ACM和IEEE嵌入式软件国际会议论文集,2008。
Regehr 2010年的另一篇博客文章,使用volatile破解系统代码的九种方法。
温特穆特对挥发性及其有害影响的回答。
这些是关于编译器错误和程序员错误的报告。但他们展示了volatile
应该如何工作,并且这个答案符合这些规范。
您会发现每个编译器都有一个扩展来实现这一点。
GCC:
__attribute__((noinline))
int zero()
{
return 0;
}
MSVC:
__declspec(noinline)
int zero()
{
return 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。
- 努力将整数转换为链表。不知道我在这里做错了什么
- 我正在使用嵌套的while循环来解析具有多行的文本文件,但由于某种原因,它只通过第一行,我不知道为什么
- 这个指针和内存代码打印是什么?我不知道是打印垃圾还是如何打印我需要的值
- 叮当不知道PTRDIFF_MAX?
- 如何在不知道向量大小的情况下输入向量内部的向量?
- 我正在尝试使用 c++ 创建一个货币转换程序,我不知道如何继续
- 不知道某个东西是否被忽略会引入未定义的行为吗
- 如何在C++中读取空格分隔的输入 当我们不知道输入的数量时
- 我不知道这条线是做什么的
- 如何在不知道C++中有多少可选参数的情况下在循环中使用va_arg?
- 在不知道套接字的情况下关闭网络连接
- 如果我不知道每个列表中有多少个数字,我如何将给定数量的数字列表作为输入?
- 我不知道导致错误的原因 (C3074)
- 我不知道为什么这段代码会让核心被转储?
- 我正在尝试制作一个自平衡机器人,但编译时存在错误。我不知道如何解决它
- 循环通过网格获取温度,但不知道如何告诉程序停止循环
- 如何在不知道对应关系的情况下在字符串中搜索字符并将其分配给另一个字符?
- 反转字符串.不知道为什么这个逻辑是错误的.C++
- 不知道如何在家庭作业任务中实现一件事
- 创建一个始终返回零但优化器不知道的函数