Reputation: 11598
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 can see that the counter gets incremented when I press CTR+C. With -O1
, this is optimized away. Why is that and how could I avoid it?
Upvotes: 3
Views: 179
Reputation: 158599
It looks like the following section of the draft C++11 standard is relevant section 1.9
[intro.execution]:
When the processing of the abstract machine is interrupted by receipt of a signal, the values of objects which are neither
- of type volatile std::sig_atomic_t nor
- lock-free atomic objects (29.4)
are unspecified during the execution of the signal handler, and the value of any object not in either of these two categories that is modified by the handler becomes undefined.
Since counter
is nether volatile nor an atomic object the value is unspecified and therefore the compiler is allowed to optimize it away via the as-if rule.
The wording changed in C++14 draft and we have the following:
If a signal handler is executed as a result of a call to the raise function, then the execution of the handler is sequenced after the invocation of the raise function and before its return. [ Note: When a signal is received for another reason, the execution of the signal handler is usually unsequenced with respect to the rest of the program. —end note ]
which seems to leave it unspecified in a sense, since the it is only the note that says the sequence handler is unsequenced, but if we read N3910: N3910: What can signal handlers do? (CWG 1441) we can see that this seems to be considered a data race and thus undefined behavior.
Upvotes: 4
Reputation: 283803
Your code exhibits undefined behavior, according to the progress guarantee rule in section 1.10:
The implementation may assume that any thread will eventually do one of the following:
- terminate,
- make a call to a library I/O function,
- access or modify a volatile object, or
- perform a synchronization operation or an atomic operation.
[ Note: This is intended to allow compiler transformations such as removal of empty loops, even when termination cannot be proven. — end note ]
Because your loop does none of these, the optimizer may assume the loop is never entered, and remove it completely.
Upvotes: 1