为啥 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 会给出不同的答案

为啥检查没有被优化

为啥 GCC 不优化结构?

为啥在 GCC 5.1 中仍然启用 COW std::string 优化?

为啥 GCC 没有尽可能地优化这组分支和条件?

如果我优化大小而不是速度,为什么GCC会生成15-20%的代码?