C++ Signalverarbeitung meistern: Sichere Interrupt-Verwaltung und saubere Programmausführung
Was sind Signale?
Signale sind Unterbrechungen, die vom Betriebssystem an einen Prozess gesendet werden und ein Programm vorzeitig beenden können. Sie können Interrupts erzeugen, indem Sie auf UNIX-, Linux-, macOS- oder Windows-Systemen Strg+C drücken.
Es gibt Signale, die ein Programm nicht abfangen kann, aber die folgende Liste zeigt Signale, die Sie in Ihrem Programm abfangen und darauf reagieren können. Diese Signale sind im C++-Header <csignal> definiert.
| Nr. | Signal & Beschreibung |
|---|---|
| 1 | SIGABRT – Anormale Beendigung des Programms, z. B. durch einen Aufruf von abort(). |
| 2 | SIGFPE – Fehlerhafte arithmetische Operation, wie Division durch Null oder Überlauf. |
| 3 | SIGILL – Erkennung einer ungültigen Instruktion. |
| 4 | SIGINT – Empfang eines interaktiven Aufmerksamkeits-Signals (z. B. Strg+C). |
| 5 | SIGSEGV – Ungültiger Speicherzugriff (Segmentation Fault). |
| 6 | SIGTERM – Beendigungsanforderung, die an das Programm gesendet wird. |
Die signal()-Funktion
Die C++-Signalbehandlungsbibliothek stellt die Funktion signal bereit, um unerwartete Ereignisse abzufangen. Die Syntax lautet:
void (*signal(int sig, void (*func)(int)))(int);
Vereinfacht nimmt diese Funktion zwei Argumente entgegen: das erste ist eine Ganzzahl für die Signalnummer, das zweite ein Zeiger auf die Signalbehandlungsfunktion.
Beispiel: SIGINT abfangen
Das folgende Programm registriert einen Handler für SIGINT und beendet sich sauber, wenn der Benutzer Strg+C drückt.
#include <iostream>
#include <csignal>
#include <unistd.h> // für sleep()
using namespace std;
void signalHandler(int signum) {
cout << "Interrupt-Signal (" << signum << ") empfangen.\n";
// Bereinigung und Ressourcenfreigabe hier
exit(signum);
}
int main() {
// SIGINT registrieren
signal(SIGINT, signalHandler);
while (true) {
cout << "Gehe schlafen...." << endl;
sleep(1);
}
return 0;
}
Ausgabe nach Drücken von Strg+C:
Gehe schlafen....
Gehe schlafen....
Gehe schlafen....
Interrupt-Signal (2) empfangen.
Die raise()-Funktion
Sie können Signale auch programmintern mit raise() erzeugen. Die Syntax:
int raise(int sig);
Der Parameter sig ist die Signalnummer (z. B. SIGINT, SIGABRT, SIGFPE, …).
Beispiel: Internes Auslösen von SIGINT
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
void signalHandler(int signum) {
cout << "Interrupt-Signal (" << signum << ") empfangen.\n";
exit(signum);
}
int main() {
int i = 0;
signal(SIGINT, signalHandler);
while (++i) {
cout << "Gehe schlafen...." << endl;
if (i == 3) {
raise(SIGINT); // Signal selbst auslösen
}
sleep(1);
}
return 0;
}
Ausgabe:
Gehe schlafen....
Gehe schlafen....
Gehe schlafen....
Interrupt-Signal (2) empfangen.
Wichtige Hinweise
- Signal-Handler sollten so einfach wie möglich gehalten werden (nur asynchronsignal-sichere Funktionen aufrufen).
- Verwenden Sie
volatile sig_atomic_tfür Flags, die zwischen Handler und Hauptprogramm geteilt werden. - In modernen C++-Anwendungen (C++11 und neuer) sollten Sie für komplexere Synchronisation eher
std::atomic, Condition Variables oder Threads in Betracht ziehen.
C Sprache
- C++-Datentypen verstehen: Grundlagen, Modifizierer und Best Practices
- C++-Operatoren meistern: Ein vollständiger Leitfaden mit praktischen Beispielen
- C++-Kommentare: Best Practices für lesbaren, wartbaren Code
- C++ Klassentemplates meistern: Ein praktischer Leitfaden
- C-Dateiverwaltung: I/O mit fopen, fprintf, fread & fseek meistern
- C++ Exception Handling meistern: Praktische try, catch, throw Beispiele
- Fehlerbehandlung in C meistern: Best Practices & Techniken
- C++ Programmiersprache: Überblick & Kernfeatures
- C++ Konstanten & Literale: Typen, Syntax & Best Practices
- C++ Operatoren: Typen, Nutzung & Best Practices