The C++ Standard, [support.runtime], paragraph 10 [ISO/IEC 14882-2014], states the following:
The common subset of the C and C++ languages consists of all declarations, definitions, and expressions that may appear in a well-formed C++ program and also in a conforming C program. A POF (“plain old function”) is a function that uses only features from this common subset, and that does not directly or indirectly use any function that is not a POF, except that it may use plain lock-free atomic operations. A plain lock-free atomic operation is an invocation of a function f from Clause 29, such that f is not a member function, and either f is the function atomic_is_lock_free, or for every atomic argument A passed to f, atomic_is_lock_free(A) yields true. All signal handlers shall have C linkage. The behavior of any function other than a POF used as a signal handler in a C++ program is implementation-defined.228
Footnote 228 states the following:
In particular, a signal handler using exception handling is very likely to have problems. Also, invoking
std::exitmay cause destruction of objects, including those of the standard library implementation, which, in general, yields undefined behavior in a signal handler.
Noncompliant Code Example
In this noncompliant code example, the signal handler is declared as a
static function. However, since all signal handler functions must have C language linkage, and C++ is the default language linkage for functions in C++, calling the signal handler results in undefined behavior.
This compliant solution defines
sig_handler() as having C language linkage. As a consequence of declaring the signal handler with C language linkage, the signal handler will have external linkage rather than internal linkage.
Noncompliant Code Example
In this noncompliant code example, a signal handler calls a function that allows exceptions, and it attempts to handle any exceptions thrown. Because exceptions are not part of the common subset of C and C++ features, this example results in implementation-defined behavior. However, it is unlikely that the implementation's behavior will be suitable. For instance, on a stack-based architecture where a signal is generated asynchronously (instead of as a result of a call to
std::raise()), it is possible that the stack frame is not properly initialized, causing stack tracing to be unreliable and preventing the exception from being caught properly.
There is no compliant solution whereby
g() can be called from the signal handler because it allows exceptions. Even if
g() were implemented such that it handled all exceptions and was marked
noexcept(true), it would still be noncompliant to call
g() from a signal handler because
g() would still use a feature that is not a part of the common subset of C and C++ features allowed by a signal handler. Therefore, this compliant solution removes the call to
g() from the signal handler and instead polls a variable of type
volatile sig_atomic_t periodically; if the variable is set to
1 in the signal handler, then
g() is called to respond to the signal.
Failing to use a plain old function as a signal handler can result in implementation-defined behavior as well as undefined behavior. Given the number of features that exist in C++ that do not also exist in C, the consequences that arise from failure to comply with this rule can range from benign (harmless) behavior to abnormal program termination, or even arbitrary code execution.
|Properly define signal handlers|
|SEI CERT C Coding Standard||SIG30-C. Call only asynchronous-safe functions within signal handlers|
SIG31-C. Do not access shared objects in signal handlers
Subclause 18.10, "Other Runtime Support"