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

SignalValueCauseDefault Action
SIGINT2User pressed Ctrl+CTerminate
SIGTERM15Termination request sent by OS/userTerminate
SIGSEGV11Segmentation fault (invalid memory access)Terminate + core dump
SIGFPE8Floating point / arithmetic error (divide by zero)Terminate + core dump
SIGABRT6Abort called (assertion failure)Terminate + core dump
SIGALRM14Timer expired (set with alarm())Terminate
SIGUSR110User-defined signal 1Terminate
SIGUSR212User-defined signal 2Terminate

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

HandlerMeaning
SIG_DFLRestore the default behavior for that signal
SIG_IGNIgnore the signal completely
function_nameCall 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_t for variables shared between main code and handlers
  • Never call malloc() or free() 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.

Leave a Comment

Your email address will not be published. Required fields are marked *