For portability, one should probably use std::signal
from the standard C++ library, but there is a lot of restriction on what a signal handler can do. Unfortunately, it is not possible to catch a SIGSEGV from within a C++ program without introducing undefined behavior because the specification says:
abort
, exit
, some atomic functions, reinstall current signal handler, memcpy
, memmove
, type traits, `std::move, std::forward
, and some more).throw
expression.This proves that it is impossible to catch SIGSEGV from within a program using strictly standard and portable C++. SIGSEGV is still caught by the operating system and is normally reported to the parent process when a wait family function is called.
You will probably run into the same kind of trouble using POSIX signal because there is a clause that says in 2.4.3 Signal Actions:
The behavior of a process is undefined after it returns normally from a signal-catching function for a SIGBUS, SIGFPE, SIGILL, or SIGSEGV signal that was not generated by
kill()
,sigqueue()
, orraise()
.
A word about the longjump
s. Assuming we are using POSIX signals, using longjump
to simulate stack unwinding won't help:
Although
longjmp()
is an async-signal-safe function, if it is invoked from a signal handler which interrupted a non-async-signal-safe function or equivalent (such as the processing equivalent toexit()
performed after a return from the initial call tomain()
), the behavior of any subsequent call to a non-async-signal-safe function or equivalent is undefined.
This means that the continuation invoked by the call to longjump cannot reliably call usually useful library function such as printf
, malloc
or exit
or return from main without inducing undefined behavior. As such, the continuation can only do a restricted operations and may only exit through some abnormal termination mechanism.
To put things short, catching a SIGSEGV and resuming execution of the program in a portable is probably infeasible without introducing UB. Even if you are working on a Windows platform for which you have access to Structured exception handling, it is worth mentioning that MSDN suggest to never attempt to handle hardware exceptions: Hardware Exceptions.
At last but not least, whether any SIGSEGV would be raised when dereferencing a null valued pointer (or invalid valued pointer) is not a requirement from the standard. Because indirection through a null valued pointer or any invalid valued pointer is an undefined behaviour, which means the compiler assumes your code will never attempt such a thing at runtime, the compiler is free to make code transformation that would elide such undefined behavior. For example, from cppreference,
int foo(int* p) {
int x = *p;
if(!p)
return x; // Either UB above or this branch is never taken
else
return 0;
}
int main() {
int* p = nullptr;
std::cout << foo(p);
}
Here the true path of the if
could be completely elided by the compiler as an optimization; only the else
part could be kept. Said otherwise, the compiler infers foo()
will never receive a null valued pointer at runtime since it would lead to an undefined behaviour. Invoking it with a null valued pointer, you may observe the value 0
printed to standard output and no crash, you may observe a crash with SIGSEG, in fact you could observe anything since no sensible requirements are imposed on programs that are not free of undefined behaviors.