为什么GCC会优化掉这个自增操作?

3
请查看以下代码:

考虑下面的代码:

#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,这个过程被优化掉了。为什么会这样,我该如何避免这种情况?


5
哎呀,我忘记加上 volatile 关键字了。 - d33tah
这可能是相关的:https://dev59.com/Gm_Xa4cB1Zd3GeqPxyjs - Fred Larson
1
еңЁдҝЎеҸ·еӨ„зҗҶзЁӢеәҸдёӯпјҢеҸӘжңүеҪ“е…ұдә«еҸҳйҮҸзҡ„зұ»еһӢдёәvolatile sig_atomic_tпјҲжҲ–C++11дёӯзҡ„std::atomicзұ»еһӢд№ӢдёҖпјүж—¶пјҢжүҚиғҪи®ҝй—®е®ғ们гҖӮ - Kerrek SB
对于那位因为问题不可重现而投票关闭的人,事实上您可以在此处实时查看 - Shafik Yaghmour
1
另外,由于C++14中易变性的情况发生了变化,因此这不再是一个有效的解决方案。 - Shafik Yaghmour
显示剩余2条评论
2个回答

4
看起来草案C++11标准的以下部分是相关的,即第1.9[intro.execution]:

当抽象机的处理因接收信号而中断时,既非volatile std::sig_atomic_t类型也非无锁原子对象(29.4)的对象的值在信号处理程序执行期间未指定,并且由处理程序修改的不属于这两个类别的任何对象的值变得未定义。

由于counter既不是volatile也不是atomic object,其值是未指定的,因此编译器可以通过as-if rule将其优化掉。
C++14草案中的措辞有所改变,我们有以下内容:

如果调用raise函数导致执行信号处理程序,则在调用raise函数之后、返回之前对处理程序的执行进行排序。 [注意:当由于其他原因接收到信号时,信号处理程序的执行通常与程序的其他部分无序。 —end note ]

这似乎使其成为未指定的情况,因为只有注释说序列处理程序是无序的,但是如果我们阅读N3910: N3910: What can signal handlers do? (CWG 1441),我们可以看到这似乎被认为是数据竞争,因此是未定义的行为。

C++14中的措辞已经改变。不幸的是,它变得更加复杂了,我再也无法理解它了。 - Brian Bi
@Brian 是的,我添加了一篇论文的链接,似乎是引入了新措辞。虽然很长,但基本上看起来像是数据竞争,即未定义行为,但我需要更详细地阅读它。 - Shafik Yaghmour

1
根据第1.10节的进度保证规则,您的代码表现出未定义行为:

实现可以假定任何线程最终都会执行以下操作之一:

  • 终止,
  • 调用库I/O函数,
  • 访问或修改易失性对象,或者
  • 执行同步操作或原子操作。

【注意】这旨在允许编译器进行转换,例如删除空循环,即使无法证明终止。 ——注释结束

由于您的循环没有执行上述任何操作,优化器可能会假设循环从未被执行,并将其完全删除。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接