为啥 GCC 会优化掉这种增量?
Posted
技术标签:
【中文标题】为啥 GCC 会优化掉这种增量?【英文标题】:Why does GCC optimize away this incrementation?为什么 GCC 会优化掉这种增量? 【发布时间】:2015-04-17 19:53:33 【问题描述】:考虑以下代码:
#include <stdlib.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
uint64_t counter = 0;
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
void sig_handler(int signo)
printf( "%" PRIu64 "\n", counter);
int main()
struct sigaction act;
act.sa_handler = &sig_handler;
sigaction(SIGINT, &act, NULL);
for( ;; )
counter++;
return 0;
如果我使用-O0
编译代码,我可以看到当我按下 CTR+C 时计数器会增加。使用-O1
,这已被优化掉。为什么会这样?我该如何避免?
【问题讨论】:
哎呀,我忘了加volatile
。
这可能是相关的:***.com/q/15187459/10077
在信号处理程序中,您只能访问类型为volatile sig_atomic_t
(或C++11 中std::atomic
类型之一)的共享变量。
对于投票关闭 b/c 的人是不可复制的,确实可以see it live here。
另外,由于 C++14 中的易失性变化不再是有效的解决方案
【参考方案1】:
看起来 C++11 标准草案的以下部分是相关部分 1.9
[intro.execution]:
当抽象机的处理被收据中断时 一个信号的对象的值,两者都不是
类型为 volatile std::sig_atomic_t nor 无锁原子对象 (29.4)在信号处理程序执行期间未指定,并且 不在这两个类别中的任何一个对象的值 被处理程序修改的变为未定义。
由于counter
既不是volatile 也不是原子对象,因此该值未指定,因此允许编译器通过as-if rule 对其进行优化。 p>
C++14 草案中的措辞发生了变化,我们有以下内容:
如果一个信号处理程序由于调用 raise 函数而被执行,那么该处理程序的执行是 在调用 raise 函数之后和返回之前排序。 [注:收到信号时 由于另一个原因,信号处理程序的执行通常相对于其余部分是无序的 程序。 ——尾注]
这似乎在某种意义上使它未指定,因为它只是说明序列处理程序未排序的注释,但如果我们阅读N3910: N3910: What can signal handlers do? (CWG 1441),我们可以看到这似乎被视为数据竞争,因此未定义行为。
【讨论】:
C++14 中的措辞发生了变化。不幸的是它变得更复杂了,我无法理解了。 @Brian 是的,我在论文中添加了一个链接,似乎引入了新的措辞,它很长,但基本上它看起来像是一场数据竞赛,即未定义的行为,但我必须更详细地阅读它.【参考方案2】:根据第 1.10 节中的进度保证规则,您的代码表现出未定义的行为:
实现可能假设任何线程最终都会执行以下操作之一:
终止, 调用库 I/O 函数, 访问或修改易失性对象,或 执行同步操作或原子操作。[ 注意:这旨在允许编译器转换,例如删除空循环,即使无法证明终止。 ——尾注]
因为您的循环不执行这些操作,优化器可能会假定循环从未进入,并将其完全删除。
【讨论】:
以上是关于为啥 GCC 会优化掉这种增量?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在 gcc 和 clang 上通过优化将大 double 转换为 uint16_t 会给出不同的答案