In C++, signals are the interrupts, which are delivered to a process by the operating system to stop its ongoing task and attend to the task for which the interrupt has been generated. Interrupts are used to notify the program of specific events or conditions, such as an error, a termination request, or an external input. When a signal is received, the operating system stops the program's usual execution to handle it.
In C++, signals can also be generated by the operating system on the basis of system or error conditions. We can generate interrupts by pressing Ctrl+ C on Linux , UNIX, Mac OS X, or Windows system.
When a signal is given, the system can:
- Terminate the program.
- Pause it
- Just ignore it.
- Alternatively, invoke a programmer-defined custom handler function.
Catching and Handling Signals
In C++ , not all signals can be caught or handled. Some signals are non-catchable, such as SIGKILL or SIGSTOP, and the operating system strictly enforces them to ensure system stability. Many other signals, such as SIGINT, SIGFPE, SIGSEGV, and SIGTERM, can be intercepted and handled within a program.
If we want to work with signals in C++, utilize the <csingnal> header, which provides signal names and functions, such as signal to associate a handler with a signal.
C++ Simple Signal Handling Example:
Let us take an example to illustrate the signal handling in C++.
Example
#include <iostream>
using namespace std; //using standard namespace
int main() //main function
{
while (true)
{
cout << "Hello, World!!\n";
}
return 0;
}
Output:
Hello, World!!
Hello, World!!
Hello, World!!
Hello, World!!
Hello, World!!
Explanation:
In this example, we create an infinite loop that writes the message "Hello, World!!" to the terminal. When the user presses Ctrl+C, the system generates the SIGINT signal, which interrupts and terminates the currently running program.
Common Signals in C++ with their Purpose
Here is a list of several types of signals in C++. Some of them are as follows:
| Signal | Description |
|---|---|
| SIGABRT | It occurs when a program calls the abort() function. It leads to abnormal termination. |
| SIGFPE | Triggered by errors in arithmetic, such as division by zero or numeric overflow. |
| SIGILL | It is used when the processor detects an illegal or undefined machine instruction. |
| SIGINT | It is used when the user interrupts the program (e.g., pressing Ctrl+C). |
| SIGSEGV | It occurs when a program tries to access memory it doesn't have permission to, like dereferencing a null or invalid pointer. |
| SIGTERM | It is commonly used to send the termination request using the kill or other methods. |
| SIGHUP | It is used when a terminal is closed or the controlling process ends. It is used to detect hang-ups. |
| SIGQUIT | It is similar to SIGINT, but also generates a core dump. It typically triggered by Ctrl+\. |
| SIGTRAP | It is used by debuggers to trigger breakpoints or trap conditions. |
| SIGBUS | It is raised when a process accesses memory incorrectly at a hardware level (e.g., misaligned memory access). |
| SIGUSR1 | It is reserved for user-defined behavior. Developers can assign custom actions when this signal is received. |
| SIGUSR2 | It is another user-defined signal, similar to SIGUSR1. |
| SIGALRM | It is used when a timer set by the alarm() function expires. Commonly used for timeouts. |
| SIGCONT | It is used to resume a process that has been paused (e.g., after SIGSTOP). |
| SIGSTOP | It stops (pauses) a process immediately. It cannot be caught, blocked, or ignored. |
Signal function
In C++, the signal function is a part of the signal-handling library, which is described in the header. It allows our program to respond to unexpected events, such as interrupts or unlawful operations by customizing signal handling.
Syntax
It has the following syntax:
void (*signal(int sig, void *func(int)))(int);
In this syntax:
- sig: The signal number (e.g., SIGINT, SIGTERM) that we want to handle.
- func: A pointer to a function to be called when the signal is received.
- return value: The return value is a pointer to the preceding signal handler.
Ways to Handle Signals with signal function
If we call the signal function, we have to specify how our program should respond to the provided signal. We have three primary options:
1) Default Action - SIG_DFL
In C++, it instructs the system to apply the signal's default behavior. For example, the program might terminate or create a core dump.
2) Ignore Signal - SIG_IGN
It allows the application to completely disregard the signal. The signal will be detected but not acted on. Execution continues because if nothing has happened.
3) Custom Function Handler
In C++, we can write our own signal-handling function. When the signal is received, the handler function is called, and we can specify how the application should respond (such as saving data, print a message, clean up resources, etc.).
C++ Signal Handling Example
Let us take an example to illustrate the signal handling in C++.
Example
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std; //using standard namespace
void my_handler(int signum)
{
cout << "\nInterrupt signal (" << signum << ") received. Program will now terminate.\n";
exit(signum);
}
int main() //main function
{
signal(SIGINT, my_handler);
cout << "The program is running. Press Ctrl+C to stop the flow..\n";
while (true)
{
cout << "Running...\n";
sleep(1);
}
return 0;
}
Output:
The program is running. Press Ctrl+C to stop the flow..
Running...
Running...
Running...
Running...
Running...
Explanation:
In this example, we use an infinite loop to print "Running..." every second. After that, when the user presses Ctrl+C, the custom handler my_handler receives the SIGINT signal, prints a termination message, and gracefully stops the program.
raise Function
In C++, the raise function sends a signal to the currently running process. It is simple to use and suitable for testing signal handlers within the program.
Syntax
It has the following syntax:
int raise(int signal_type);
In this syntax,
signal_type: The signal number to be sent for handling. It can take one of the following values:
- SIGINT
- SIGABRT
- SIGFPE
- SIGILL
- SIGSEGV
- SIGTERM
- SIGHUP
Return Value: If the signal was properly delivered, it returns 0. If it fails, it returns a value other than zero.
C++ raise function Example
Let us take an example to illustrate the raise function in C++.
Example
#include <csignal>
#include <iostream>
using namespace std; //using standard namespace
void Signal_Handler(int s)
{
cout << "Interrupt is handled. Signal number is: " << s << endl;
exit(s);
}
int main() //main function
{
signal(SIGINT, Signal_Handler);
raise(SIGINT);
return 0;
}
Output:
Interrupt is handled. Signal number is: 2
Explanation:
In this example, we use the raise function to manually generate the SIGINT signal, which is generally emitted by pressing Ctrl+C. When the raise(SIGINT) function is used, the custom handler Signal_Handler receives the signal and prints the signal number before terminating the application.
C++ Example using several raise Functions
Let us take an example to illustrate the several raise function in C++.
Example
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std; //using standard namespace
void Signal_Handler(int sig_no)
{
cout << "Interrupt is handled and the Signal number is: " << sig_no << endl;
}
void DisplayingMenu()
{
cout << "\nChoose a signal to raise which displayed below:\n";
cout << "1. SIGINT (Ctrl+C simulation)\n";
cout << "2. SIGILL (Illegal Instruction)\n";
cout << "3. SIGALRM (Timer Alarm)\n";
cout << "4. SIGFPE (Division by Zero)\n";
cout << "5. SIGSEGV (Invalid Memory Access)\n";
cout << "6. Exit out of the program.\n";
cout << "Enter your choice which is from 1 to 6: ";
}
int main() //main function
{
signal(SIGINT, Signal_Handler);
signal(SIGILL, Signal_Handler);
signal(SIGALRM, Signal_Handler);
signal(SIGFPE, Signal_Handler);
signal(SIGSEGV, Signal_Handler);
int user_choice;
while (true)
{
DisplayingMenu();
cin >> user_choice;
switch (user_choice)
{
case 1:
raise(SIGINT);
break;
case 2:
raise(SIGILL);
break;
case 3:
alarm(1);
sleep(2);
break;
case 4:
{
int p = 30, q = 0;
int ans = p / q;
cout << "The Result is: " << ans << endl;
break;
}
case 5:
{
int* pntr = nullptr;
*pntr = 25;
break;
}
case 6:
cout << "Exiting the program." << endl;
return 0;
default:
cout << "Invalid choice. Try again.\n";
}
}
return 0;
}
Output:
Choose a signal to raise which displayed below:
1. SIGINT (Ctrl+C simulation)
2. SIGILL (Illegal Instruction)
3. SIGALRM (Timer Alarm)
4. SIGFPE (Division by Zero)
5. SIGSEGV (Invalid Memory Access)
6. Exit out of the program.
Enter your choice which is from 1 to 6: 1
Interrupt is handled and the Signal number is: 2
Choose a signal to raise which displayed below:
1. SIGINT (Ctrl+C simulation)
2. SIGILL (Illegal Instruction)
3. SIGALRM (Timer Alarm)
4. SIGFPE (Division by Zero)
5. SIGSEGV (Invalid Memory Access)
6. Exit out of the program.
Enter your choice which is from 1 to 6: 2
Interrupt is handled and the Signal number is: 4
Choose a signal to raise which displayed below:
1. SIGINT (Ctrl+C simulation)
2. SIGILL (Illegal Instruction)
3. SIGALRM (Timer Alarm)
4. SIGFPE (Division by Zero)
5. SIGSEGV (Invalid Memory Access)
6. Exit out of the program.
Enter your choice which is from 1 to 6: 3
Interrupt is handled and the Signal number is: 14
Choose a signal to raise which displayed below:
1. SIGINT (Ctrl+C simulation)
2. SIGILL (Illegal Instruction)
3. SIGALRM (Timer Alarm)
4. SIGFPE (Division by Zero)
5. SIGSEGV (Invalid Memory Access)
6. Exit out of the program.
Enter your choice which is from 1 to 6: 6
Exiting the program.
Explanation:
In this example, we register custom handlers for five distinct signals, and a menu-driven loop allows the user to manually raise signals, such as SIGINT, SIGILL, SIGALRM, SIGFPE, and SIGSEGV. Depending on the user's preference, each signal is purposefully triggered, and the handler prints the signal number rather than terminating the program unless a fatal error occurs.
kill function
Unlike the raise function, which only affects the current process, the kill function can send signals to any process (including the current one) as long as we have permission.
Syntax
It has the following syntax:
int kill(pid_t pid, int signal_type);
- pid: The process ID of the target process.
- Returns 0 for success and -1 for failure.
C++ kill function Example
Let us take an example to illustrate the kill function in C++.
Example
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std; //using standard namespace
void Handle_Signal(int s)
{
cout << "Signal received: " << s << endl;
}
int main() //main function
{
signal(SIGINT, Handle_Signal);
pid_t pid = getpid();
kill(pid, SIGINT);
return 0;
}
Output:
Signal received: 2
Explanation:
In this example, we utilize the kill function to deliver the SIGINT signal to its own process, which is identified by the process ID returned by getpid. When the signal is sent, the custom handler Handle_Signal is invoked, which displays the signal number on the console.
Conclusion
In conclusion, the signal handling in C++ enables programs to respond to unexpected runtime events, such as interrupts or termination requests. If we use the signal function and define signal handler functions, we can intercept and manage signals, such as SIGINT or SIGTERM. It helps to improve the robustness of the program, which ensures safer shutdowns and controlled behavior during exceptional situations. Overall, signal handling is especially valuable in systems programming and resource-critical applications.
C++ Signal Handling FAQs
1) What signal is used when a user presses Ctrl + C?
The SIGINT signal is sent when the user pushes Ctrl + C in the terminal.
2) Can a program ignore a signal?
Yes, a program can ignore a signal by setting its handler to SIG_IGN with the signal function.
3) What signal cannot be caught, blocked, or ignored in C++?
No, program can catch, block, or ignore the SIGKILL and SIGSTOP signals.
4) What is the purpose of C++'s raise function?
In C++, the raise function is used to generate a signal within the running process manually.
5) What function sends a signal to another process using its process ID?
In C++, the kill function is used to deliver a signal to a specific process by referencing its PID.