Signal handling in C++ enables programs to respond to asynchronous events or signals from the operating system.
Signals are used to notify a program of events like segmentation faults, interrupts, or termination requests. The <csignal> library provides functions for handling signals, allowing a program to intercept these events and take appropriate action.
In this tutorial, we’ll cover:
1. Basic Signal Handling with signal
The <csignal> library provides the signal function to set up a signal handler. A signal handler is a function that executes when a specified signal is received by the program.
Syntax
#include <csignal> void signal(int signal_number, void (*handler)(int));
- signal_number: The signal to handle (e.g., SIGINT for an interrupt signal).
- handler: A function that takes an integer signal number as an argument. This function executes when the signal is received.
Example: Handling SIGINT (Interrupt Signal)
The following example demonstrates how to set up a signal handler for SIGINT, typically sent when the user presses Ctrl+C.
#include <iostream> #include <csignal> void signalHandler(int signal) { std::cout << "Interrupt signal (" << signal << ") received.\n"; exit(signal); // Exit the program } int main() { // Register signal handler for SIGINT signal(SIGINT, signalHandler); std::cout << "Press Ctrl+C to exit the program.\n"; // Infinite loop to keep the program running while (true) {} return 0; }
Output:
Press Ctrl+C to exit the program. # Press Ctrl+C Interrupt signal (2) received.
In this example:
- signal(SIGINT, signalHandler); registers signalHandler as the handler for SIGINT.
- When the user presses Ctrl+C, the signalHandler function is called, printing a message and exiting the program.
2. Commonly Used Signals
Here are some commonly used signals in C++:
- SIGINT: Interrupt signal, usually generated by pressing Ctrl+C.
- SIGTERM: Termination request sent to the program (e.g., when terminating a process).
- SIGSEGV: Segmentation fault, usually due to invalid memory access.
- SIGFPE: Floating-point exception (e.g., division by zero).
- SIGABRT: Abnormal termination (abort).
You can use these signals in your program to handle specific events.
3. Handling Multiple Signals
You can register multiple signal handlers by calling signal multiple times for different signals.
#include <iostream> #include <csignal> void handleInterrupt(int signal) { std::cout << "Interrupt signal (" << signal << ") received.\n"; } void handleTerminate(int signal) { std::cout << "Termination signal (" << signal << ") received.\n"; } int main() { // Register signal handlers for SIGINT and SIGTERM signal(SIGINT, handleInterrupt); signal(SIGTERM, handleTerminate); std::cout << "Press Ctrl+C to trigger SIGINT, or send SIGTERM to this process.\n"; // Infinite loop to keep the program running while (true) {} return 0; }
Output:
Press Ctrl+C to trigger SIGINT, or send SIGTERM to this process. # Press Ctrl+C Interrupt signal (2) received. # Send SIGTERM Termination signal (15) received.
In this example:
- signal(SIGINT, handleInterrupt); registers a handler for SIGINT.
- signal(SIGTERM, handleTerminate); registers a handler for SIGTERM.
4. Ignoring Signals
You can ignore a signal by setting its handler to SIG_IGN.
#include <iostream> #include <csignal> int main() { // Ignore SIGINT (Ctrl+C) signal(SIGINT, SIG_IGN); std::cout << "SIGINT (Ctrl+C) is ignored. Press Ctrl+Z to terminate.\n"; // Infinite loop to keep the program running while (true) {} return 0; }
Output:
SIGINT (Ctrl+C) is ignored. Press Ctrl+Z to terminate. # Press Ctrl+C # Nothing happens, the signal is ignored.
In this example:
- signal(SIGINT, SIG_IGN); tells the program to ignore the SIGINT signal.
- Pressing Ctrl+C does not interrupt the program, as the signal is ignored.
5. Custom Signal Handlers
You can define custom signal handler functions to perform specific actions when a signal is received. The handler function must have a single integer parameter.
Example: Handling Division by Zero with SIGFPE
In this example, we handle a division by zero error by setting a handler for the SIGFPE (Floating-Point Exception) signal.
#include <iostream> #include <csignal> void divideByZeroHandler(int signal) { std::cout << "Floating-point exception (division by zero) occurred! Signal: " << signal << std::endl; exit(signal); } int main() { // Register the SIGFPE handler signal(SIGFPE, divideByZeroHandler); int a = 5; int b = 0; std::cout << "Attempting to divide by zero...\n"; int c = a / b; // This will trigger SIGFPE std::cout << "Result: " << c << std::endl; return 0; }
Output:
Attempting to divide by zero... Floating-point exception (division by zero) occurred! Signal: 8
In this example:
- signal(SIGFPE, divideByZeroHandler); registers divideByZeroHandler to handle SIGFPE.
- When the program attempts to divide by zero, SIGFPE is triggered, and the custom handler is called.
6. Resetting Default Behavior for Signals
You can reset a signal’s behavior to its default by setting its handler to SIG_DFL.
#include <iostream> #include <csignal> void customHandler(int signal) { std::cout << "Caught signal " << signal << ", resetting to default behavior.\n"; signal(SIGINT, SIG_DFL); // Reset SIGINT to default behavior } int main() { // Register custom signal handler for SIGINT signal(SIGINT, customHandler); std::cout << "Press Ctrl+C once to trigger the handler, and again to exit with default behavior.\n"; // Infinite loop to keep the program running while (true) {} return 0; }
Output:
Press Ctrl+C once to trigger the handler, and again to exit with default behavior. # Press Ctrl+C once Caught signal 2, resetting to default behavior. # Press Ctrl+C again # Program exits as SIGINT has default behavior restored.
In this example:
- signal(SIGINT, customHandler); registers customHandler to handle SIGINT.
- signal(SIGINT, SIG_DFL); within the handler resets SIGINT to its default behavior.
- The first Ctrl+C triggers the handler, and the second Ctrl+C causes the program to exit with the default SIGINT behavior.
7. Example: Handling Multiple Signals in a Real Program
This example demonstrates handling SIGINT and SIGTERM signals in a simulated process with a cleanup action.
#include <iostream> #include <csignal> #include <cstdlib> // Cleanup function to perform necessary cleanup before exiting void cleanup() { std::cout << "Performing cleanup before exiting...\n"; } // Signal handler function void handleSignal(int signal) { if (signal == SIGINT) { std::cout << "Caught SIGINT (Ctrl+C).\n"; } else if (signal == SIGTERM) { std::cout << "Caught SIGTERM (termination request).\n"; } cleanup(); // Perform cleanup before exiting exit(signal); } int main() { // Register signal handlers for SIGINT and SIGTERM signal(SIGINT, handleSignal); signal(SIGTERM, handleSignal); std::cout << "Press Ctrl+C to send SIGINT or send SIGTERM to this process.\n"; // Simulated process that runs indefinitely while (true) {} return 0; }
Output:
Press Ctrl+C to send SIGINT or send SIGTERM to this process. # Press Ctrl+C Caught SIGINT (Ctrl+C). Performing cleanup before exiting...
In this example:
- signal(SIGINT, handleSignal); and signal(SIGTERM, handleSignal); set up handleSignal to handle both SIGINT and SIGTERM.
- When either signal is caught, the program performs a cleanup operation before exiting.
Summary
In this tutorial, we covered:
- Basic Signal Handling with signal to register signal handlers
- Commonly Used Signals such as SIGINT, SIGTERM, SIGSEGV, SIGFPE, and SIGABRT
- Handling Multiple Signals by registering different handlers for each signal
- Ignoring Signals by setting the handler to SIG_IGN
- Custom Signal Handlers to perform specific actions when a signal is caught
- Resetting Default Behavior by setting the handler to SIG_DFL
Signal handling enables C++ programs to manage asynchronous events and errors effectively, providing greater control and robustness in critical applications.