The C++ standard has this to say about it in [intro.execution] :
19 If a signal handler is executed as a result of a call to the std::raise function, then the execution of the handler is sequenced after the invocation of the std::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 ]
The meaning of "unsequenced" is clarified earlier :
15 ...SNIP... [ Note: The execution of unsequenced evaluations can overlap. — end note ]
Then in [intro.races] :
20 Two actions are potentially concurrent if
(20.1) — they are performed by different threads, or
(20.2) — they are unsequenced, at least one is performed by a signal handler, and they are not both performed by the same signal handler invocation.
The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined behavior.
The special case referred to is :
21 Two accesses to the same object of type volatile std::sig_atomic_t do not result in a data race if both occur in the same thread, even if one or more occurs in a signal handler.
To sum it all up : when a signal handler (called for an asynchronous signal) accesses an object with static storage duration that is not atomic, that access is unsequenced, and when it's happening concurrently with a conflicting access (to the same object eg.), then there's a data race, resulting in undefined behavior.
Note that this can happen just as well in a single-threaded application as in a multi-threaded application. Example (substitute int with any other type that is more obviously non-atomic if desired) :
#include <csignal>
int global = 0;
void signal_handler(int signal) {
global = 0; // OOPS : this access is (typically) unsequenced
// and might happen concurrently with the access
// in main, when the interrupt happens right in
// the middle of that access
}
int main(void) {
std::signal(SIGINT, signal_handler);
while (true) {
++global; // potentially concurrent access
}
return 0;
}
std::mutexisn't signal safe for a reason.mallocis not signal safe - you should not call it from a signal handler.