C Signal Handling
A signal is a notification sent to a process (running program) to inform it that something important happened — like the user pressing Ctrl+C, a divide-by-zero error, or an illegal memory access. The operating system delivers signals asynchronously — they can arrive at any time while your program is running.
C provides tools in the <signal.h> header to catch signals and respond to them gracefully instead of letting the program simply crash or terminate.
What Signals Look Like in the Real World
Think of signals like fire alarms in a building. The alarm (signal) interrupts whatever everyone is doing and forces a specific response (evacuate). The program is the building occupant, and the signal handler is the evacuation procedure.
Common Signals in C
| Signal | Value | Cause | Default Action |
|---|---|---|---|
SIGINT | 2 | User pressed Ctrl+C | Terminate |
SIGTERM | 15 | Termination request sent by OS/user | Terminate |
SIGSEGV | 11 | Segmentation fault (invalid memory access) | Terminate + core dump |
SIGFPE | 8 | Floating point / arithmetic error (divide by zero) | Terminate + core dump |
SIGABRT | 6 | Abort called (assertion failure) | Terminate + core dump |
SIGALRM | 14 | Timer expired (set with alarm()) | Terminate |
SIGUSR1 | 10 | User-defined signal 1 | Terminate |
SIGUSR2 | 12 | User-defined signal 2 | Terminate |
The signal() Function
The signal() function registers a signal handler — a function that runs automatically when a specific signal is received.
Syntax
#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
Simplified: signal(signal_number, handler_function);
Handler options
| Handler | Meaning |
|---|---|
SIG_DFL | Restore the default behavior for that signal |
SIG_IGN | Ignore the signal completely |
function_name | Call this custom function when the signal arrives |
Example 1: Catching Ctrl+C (SIGINT)
#include <stdio.h>
#include <signal.h>
void handleCtrlC(int sig)
{
printf("\nCaught signal %d (Ctrl+C). Exiting gracefully.\n", sig);
// Perform cleanup here (save files, close connections, etc.)
// Then exit:
// exit(0);
}
int main()
{
signal(SIGINT, handleCtrlC); // register the handler
printf("Running... Press Ctrl+C to stop.\n");
while (1)
{
printf("Working...\n");
// sleep(1); // pause 1 second (POSIX systems)
for (long i = 0; i < 100000000L; i++); // busy wait
}
return 0;
}
Without the signal handler, Ctrl+C would immediately kill the program. With the handler, you can save files, close database connections, and exit cleanly before terminating.
Example 2: Ignoring a Signal
#include <stdio.h>
#include <signal.h>
int main()
{
signal(SIGINT, SIG_IGN); // ignore Ctrl+C
printf("Ctrl+C is disabled. This loop runs until process is killed.\n");
while (1) {
// Ctrl+C will not stop this
for (long i = 0; i < 100000000L; i++);
printf("Still running...\n");
}
return 0;
}
Example 3: Handling Divide by Zero (SIGFPE)
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
void handleDivByZero(int sig)
{
printf("Error: Division by zero detected (signal %d).\n", sig);
printf("Terminating safely.\n");
exit(1);
}
int main()
{
signal(SIGFPE, handleDivByZero);
int a = 10, b = 0;
int result = a / b; // triggers SIGFPE on integer division by zero
printf("Result: %d\n", result); // This line is never reached
return 0;
}
Example 4: Sending a Signal Programmatically
A program can send a signal to itself or another process using raise() or kill().
#include <stdio.h>
#include <signal.h>
void myHandler(int sig)
{
printf("Signal %d received by the program itself.\n", sig);
}
int main()
{
signal(SIGUSR1, myHandler);
printf("Raising SIGUSR1...\n");
raise(SIGUSR1); // send signal to own process
printf("Execution continues after handling the signal.\n");
return 0;
}
Output:
Raising SIGUSR1...
Signal 10 received by the program itself.
Execution continues after handling the signal.
Signal Handler Flow Diagram
Normal program execution:
main() → ... → some code → ...
Signal arrives (e.g., Ctrl+C):
↓
Program pauses at current instruction
↓
OS invokes the registered handler function: handleCtrlC()
↓
Handler runs (cleanup, logging, etc.)
↓
Handler returns (or calls exit())
↓
Program resumes (or terminates if exit() was called)
Important Rules for Signal Handlers
- Signal handlers must be fast and simple — they interrupt normal execution
- Avoid calling complex functions like
printf()inside handlers (not async-signal-safe) - Use
volatile sig_atomic_tfor variables shared between main code and handlers - Never call
malloc()orfree()inside a signal handler
Safe Pattern: Flag-Based Signal Handling
#include <stdio.h>
#include <signal.h>
volatile sig_atomic_t stopFlag = 0; // flag shared with handler
void handleSigInt(int sig)
{
stopFlag = 1; // just set the flag — nothing else
}
int main()
{
signal(SIGINT, handleSigInt);
printf("Running. Press Ctrl+C to stop.\n");
while (!stopFlag)
{
// Normal work here
for (long i = 0; i < 50000000L; i++);
}
// Cleanup happens here, safely, in main()
printf("\nStopped safely. Cleanup done.\n");
return 0;
}
Summary
Signals are asynchronous notifications sent by the OS to a running program. Common signals include SIGINT (Ctrl+C), SIGSEGV (segfault), SIGFPE (arithmetic error), and SIGTERM (termination request). The signal() function registers a custom handler function that runs when a specific signal is received. Handlers can also ignore a signal (SIG_IGN) or restore default behavior (SIG_DFL). Signal handlers should be simple and fast — the safest pattern sets a global flag inside the handler and performs actual cleanup in main(). Signal handling enables programs to shut down gracefully, log errors, save state, and respond intelligently to external interruptions.
