C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
123: Raising and Catching Signals
Imagine you're working in a large office building. You're deep in a flow state, typing away at your keyboard, totally focused on a complex problem. Suddenly, the fire alarm screams. You don't finish the sentence you're writing; you don't carefully save your file and shut down your computer. You stop immediately, stand up, and follow the evacuation plan you were taught during orientation. Once you're safely outside and the fire marshal says it was just a drill, you might head back to your desk and try to pick up where you left off.
In C, a signal is exactly like that fire alarm. It's an asynchronous notification sent to your process to tell it that something happened—usually something urgent—that requires immediate attention. The "evacuation plan" is what we call a signal handler.
Mapping the Alarm to the Code
Let's break down how that office analogy translates to the actual mechanics of the C language:
- The Alarm Trigger: This is the signal being "raised." It could be the OS telling your program it's time to die (SIGKILL), the user hitting Ctrl+C (SIGINT), or your program accidentally trying to read memory it doesn't own (SIGSEGV).
- The Orientation/Training: This is when you use the
signal()function to tell the OS, "If you ever send me signal X, I want you to run function Y." - The Evacuation: This is the execution of your handler function. The CPU literally pauses your
main()function right in its tracks, jumps to your handler, and runs it. - Returning to the Desk: After the handler finishes, the program attempts to resume exactly where it was interrupted.
Teaching Your Program How to React
Most people are used to their programs just "crashing" when they hit Ctrl+C. But if you're writing a database or a server, you can't just vanish; you need to close file handles and save state. Here is how I usually set up a basic catch for SIGINT.
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
void handle_sigint(int sig) {
printf("\nCaught signal %d! Cleaning up temporary files before exiting...\n", sig);
// In a real app, you'd delete temp files or close DB connections here
exit(0);
}
int main() {
// This is the "orientation" phase.
// We map SIGINT (Ctrl+C) to our handler function.
signal(SIGINT, handle_sigint);
printf("I'm running. Try hitting Ctrl+C to stop me!\n");
while(1) {
printf("Working hard...\n");
sleep(1);
}
return 0;
}
Triggering the Alarm Manually
You don't always have to wait for the user or the OS to send a signal. Sometimes you want your program to signal itself, or one process to signal another. We use the raise() function for the former and kill() for the latter. Don't let the name kill() fool you; it's not just for terminating processes—it's just the generic name for "send a signal."
For example, if your program detects a corrupted configuration file that makes it impossible to continue, you might call raise(SIGABRT); to trigger an abnormal termination and generate a core dump for debugging.
The Danger of Doing Too Much in a Handler
Here is a bit of hard-earned advice: keep your handlers lean. Because a signal can hit at any microsecond, it might interrupt your program while it's in the middle of updating a global variable or holding a lock. If your handler also tries to update that same variable, you've just created a race condition that is a nightmare to debug.
In the professional world, we talk about "async-signal-safe" functions. Strictly speaking, printf() isn't actually safe to use inside a signal handler because it uses internal locks. For a simple learning project, it's fine, but in a production system, I'd simply set a volatile sig_atomic_t flag to 1 and let the main() loop check that flag and exit gracefully on its own.
📋 Practical Task
Build a Graceful Shutdown State-Saver
Your task is to create a program that simulates a long-running data processor. The program should maintain a counter that increments every second. However, instead of losing the current count when the user presses Ctrl+C, the program must catch the SIGINT signal and write the final counter value to a file named shutdown_state.txt before exiting.
Requirements:
- Implement a signal handler for
SIGINT. - The handler must open
shutdown_state.txt, write the current value of the counter, and then terminate the program. - The counter must be declared in a way that the signal handler can access it (think about the
volatilekeyword). - The
main()loop should print the current count every second to show the program is active.
There are no comments for now.