Why is GCC optimizing this gain?

Consider the following code:

#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;
}

      

If I compile the code with -O0

, I see that the counter increments when I hit CTR + C. With -O1

this is optimized. Why is this and how can I avoid it?

+3


source to share


2 answers


It looks like the following section of the draft C ++ 11 standard is the corresponding 1.9

[intro.execution] section :

When the processing of the abstract machine is interrupted on receipt of a signal, the values โ€‹โ€‹of objects that are not

  • of type volatile std :: sig_atomic_t and
  • blocking atomic objects (29.4)

are not specified during the execution of a signal handler, and the value of any object not in either of these two categories changed by the handler becomes undefined.

Since it counter

is an irrelevant or atomic object, the value is not specified and therefore the compiler is allowed to optimize it using an as-if rule .



The text changed in the C ++ 14 draft and we have this:

If the signal handler is executed as a result of a call to the promoter, then the handler's execution is ordered after the call to the promoter and before it returns. [Note: when receiving a signal for another reason, the execution of the signal handler is usually independent of the rest of the program. -end note]

which seems to leave it undefined in a sense, as this is only a note that says the sequence handler is not sequence-affected, but if we read N3910: N3910: What can signal handlers do? (CWG 1441) , we can see that this seems to be seen as a data race and hence undefined behavior.

+4


source


Your code displays undefined behavior according to the execution guarantee rule in section 1.10:

An implementation can assume that any thread will eventually do one of the following:

  • finish,
  • make a call to the library I / O function,
  • accessing or modifying a mutable object, or
  • perform a sync or atomic operation.

[Note. This is intended to allow compiler conversions such as removing empty loops even if completion is not possible. - end note]



Since your loop does nothing, the optimizer can assume that the loop is never introduced and is completely removed.

+1


source







All Articles