C-Standardbibliothek – Detaillierte Erläuterung von <signal.h> und <stdarg.h>

Inhaltsverzeichnis

Einführung

Bibliotheksvariablen

Kuhong

Bibliotheksfunktionen

Beispiel

Einführung

Bibliotheksvariablen

Kuhong

Beispiel


<signal.h>

Einführung

<signal.h> ist eine der Header-Dateien in der C-Sprachstandardbibliothek, die Unterstützung für die Signalverarbeitung bietet. In Unix und Unix-ähnlichen Systemen sind Signale ein prozessübergreifender Kommunikationsmechanismus, der zur Übermittlung von Informationen über asynchrone Ereignisse wie Fehler, Ausnahmen, Unterbrechungen usw. zwischen Prozessen verwendet wird. <signal.h> Die Header-Datei definiert zugehörige Funktionen und Makros zur Signalverarbeitung.

Bitte beachten Sie bei der Verwendung der Header-Datei <signal.h> folgende Punkte:

  • Signalverarbeitungsfunktionen sollten so einfach wie möglich sein und komplexe oder zeitaufwändige Vorgänge vermeiden, da sie häufig in unterschiedlichen Kontexten ausgeführt werden.
  • Signalverarbeitungsfunktionen sollten nach Möglichkeit den Aufruf nicht wiedereintretender Funktionen vermeiden, da diese durch Signale unterbrochen werden können.
  • Einige Signale sind unzuverlässig, d. h. es gibt keine Garantie dafür, dass sie verloren gehen. Daher ist beim Umgang mit diesen Signalen äußerste Vorsicht geboten.

Im Allgemeinen stellt <signal.h> den grundlegenden Mechanismus für die Verarbeitung von Signalen in der C-Sprache bereit, der Programmierern dabei helfen kann, Code zu schreiben, der asynchrone Ereignisse verarbeitet. Da die Signalverarbeitung jedoch gleichzeitige und asynchrone Ereignisse umfasst, ist das Schreiben zuverlässigen Signalverarbeitungscodes schwierig und muss mit Vorsicht behandelt werden.

Bibliotheksvariablen

Im Folgenden sind die Variablentypen aufgeführt, die in der Header-Datei signal.h definiert sind:

<signal.h> Der Typ sig_atomic_t ist in der Header-Datei definiert und wird als Variablentyp im Signalhandler verwendet. sig_atomic_t ist ein ganzzahliger Typ, auf den als Variable für atomare Operationen in einem Signalhandler zugegriffen werden kann.

Dies bedeutet, dass Lese- und Schreibvorgänge auf Variablen vom Typ sig_atomic_t atomar sind, auch wenn asynchrone Signale verarbeitet werden. Atomare Operationen sind unterbrechungsfreie Vorgänge, die entweder vollständig oder gar nicht ausgeführt werden und nicht durch andere gleichzeitige Vorgänge unterbrochen werden. Dies ist der Schlüssel zur Gewährleistung eines sicheren Zugriffs auf Variablen innerhalb von Signalhandlern.

Der Typ sig_atomic_t wird normalerweise verwendet, um Statusinformationen oder Flags in Signalhandlern zu speichern, um das Auftreten eines Signals oder den Status eines Ereignisses anzuzeigen. Da es atomar ist, kann garantiert werden, dass beim Lesen und Schreiben von Variablen dieses Typs bei mehreren gleichzeitigen Signalhandlern keine durch Race Conditions verursachten Fehler auftreten.

Es ist zu beachten, dass sig_atomic_t nur atomare Operationen an einer einzelnen Variablen garantiert, nicht atomare Operationen zwischen mehreren Variablen.

Zusammenfassend lässt sich sagen, dass der Typ sig_atomic_t ein Variablentyp ist, der für atomare Operationen in Signalhandlern verwendet wird und ein gewisses Maß an Thread-Sicherheit bieten kann.

Kuhong

In der Header-Datei <signal.h> sind einige Makros definiert, die bei der Signalverarbeitung verwendet werden. Hier sind einige der am häufigsten verwendeten Makros:

  • SIG_DFL: Standard-Signalhandler. Wenn der Signalhandler auf SIG_DFL eingestellt ist, bedeutet dies, dass die Standardverarbeitungsmethode des Systems verwendet wird, normalerweise das Programm beendet oder das Signal ignoriert wird.
  • SIG_ERR: Zeigt einen Signalfehler an. Wenn eine Funktion SIG_ERR zurückgibt, ist ein Signalverarbeitungsfehler aufgetreten.
  • SIG_IGN: Signal ignorieren. Wenn Sie den Signalhandler auf SIG_IGN setzen, wird das Signal ignoriert, also nichts unternommen.

Zusätzlich zu den oben genannten Makros definiert <signal.h> auch eine Reihe von Makros, die mit SIG beginnen und zur Darstellung von Signalnummern unter verschiedenen Bedingungen verwendet werden, wie zum Beispiel:

  • SIGABRT: Zeigt an, dass das Programm abnormal beendet wird.
  • SIGFPE: Zeigt einen arithmetischen Operationsfehler an, z. B. eine Division durch 0 oder einen Überlauf.
  • SIGILL: Zeigt ein illegales Funktionsbild an, beispielsweise eine illegale Anweisung.
  • SIGINT: Stellt ein Interrupt-Signal dar, das normalerweise durch Drücken von Strg-C durch den Benutzer generiert wird.
  • SIGSEGV: Zeigt einen illegalen Zugriff auf den Speicher an, z. B. den Zugriff auf eine nicht vorhandene Speichereinheit.
  • SIGTERM: Stellt das an das Programm gesendete Beendigungsanforderungssignal dar.

Diese Makros können mit der Signalfunktion verwendet werden, um die Funktionalität eines Signals anzugeben, z. B. das Festlegen eines Signalhandlers, das Ignorieren des Signals oder die Verwendung des Standardhandlers.

Es ist zu beachten, dass die spezifische Implementierung und das Verhalten dieser Makros je nach Betriebssystem und Compiler variieren können.

Bibliotheksfunktionen

Die Header-Datei <signal.h> definiert Funktionen zum Einrichten und Verarbeiten von Signalen. Zwei der häufig verwendeten Funktionen sind wie folgt:

  • void (*signal(int sig, void (*func)(int)))(int): Diese Funktion wird verwendet, um eine Funktion festzulegen, die das angegebene Signalsign, also den Signalhandler, verarbeitet. Der Parameter func ist ein Zeiger auf eine Funktion, die die Aktion darstellt, die beim Empfang des Signals ausgeführt werden soll. Wenn der Funktionsparameter SIG_DFL ist, bedeutet dies, dass die Standardverarbeitungsmethode des Systems verwendet wird. Wenn er SIG_IGN ist, bedeutet dies, dass das Signal ignoriert wird. Der Rückgabewert dieser Funktion ist die Adresse des zuvor registrierten Signalhandlers.
  • int raise(int sig): Mit dieser Funktion wird das angegebene Signal sig an den aktuellen Prozess gesendet. Gibt 0 zurück, wenn das Signal erfolgreich gesendet wurde, andernfalls wird ein Wert ungleich Null zurückgegeben. Mit dieser Funktion kann getestet werden, ob der Signalhandler korrekt installiert ist oder ob das Signal korrekt verarbeitet wird.

Es ist zu beachten, dass die Signalfunktion zwar in den meisten Fällen zum Registrieren von Signalhandlern verwendet werden kann, es jedoch auf einigen spezifischen Plattformen unterschiedliche Implementierungen geben kann. Um portablen Code zu schreiben, empfiehlt es sich, die Dokumentation der jeweiligen Plattform zu konsultieren oder auf den entsprechenden Systemaufruf zu verweisen.

Da Signalhandler normalerweise in einem asynchronen Kontext ausgeführt werden, muss beim Schreiben von Signalhandlern außerdem besondere Sorgfalt angewendet werden, um Race Conditions oder anderes undefiniertes Verhalten zu vermeiden. Zu den allgemeinen Schreibtipps gehören die weitestgehende Verwendung atomarer Operationen, die Vermeidung nicht wiedereintretender Funktionen, die Nichtzuweisung von Speicher in Signalhandlern usw.

Beispiel

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

/* 信号处理程序 */
void sigint_handler(int sig) {
    printf("接收到中断信号 %d\n", sig);
}

int main() {
    /* 注册 SIGINT 信号处理程序 */
    if (signal(SIGINT, sigint_handler) == SIG_ERR) {
        perror("无法注册 SIGINT 信号处理程序");
        exit(EXIT_FAILURE);
    }

    /* 发送 SIGINT 信号 */
    printf("发送中断信号...\n");
    if (raise(SIGINT) != 0) {
        perror("无法发送 SIGINT 信号");
        exit(EXIT_FAILURE);
    }

    printf("程序正常结束。\n");
    exit(EXIT_SUCCESS);
}

Im obigen Code definieren wir zunächst eine Funktion namens sigint_handler, um das empfangene SIGINT-Signal zu verarbeiten. Dann verwenden wir in der Hauptfunktion die Signalfunktion, um den Handler des SIGINT-Signals auf sigint_handler zu setzen. Verwenden Sie dann die Raise-Funktion, um das SIGINT-Signal an den Prozess zu senden und die entsprechenden Informationen auf der Konsole auszugeben. Schließlich endet das Programm normal.​ 

Lassen Sie uns das obige Programm kompilieren und ausführen, was zu den folgenden Ergebnissen führt:

发送中断信号...
接收到中断信号 2
程序正常结束。

<stdarg.h>

Einführung

Die Header-Datei stdarg.h definiert einen Variablentyp va_list und drei Makros, mit denen die Parameter in der Funktion abgerufen werden können, wenn die Anzahl der Parameter unbekannt ist (d. h. die Anzahl der Parameter variabel ist). Funktionen mit variablen Parametern werden normalerweise mit Auslassungspunkten (,...) am Ende der Parameterliste definiert.

Bibliotheksvariablen

va_list ist der in der Headerdatei <stdarg.h> definierte Variablentyp.

Der Typ va_list ist ein Datentyp, der zum Speichern variabler Parameterinformationen verwendet wird. Normalerweise definieren wir eine Variable vom Typ va_list, um die Variablenparameterfunktion zu betreiben.

Bei spezieller Verwendung initialisieren Sie die Variable va_list, indem Sie das Makro va_start aufrufen und auf den ersten Parameter in der Parameterliste verweisen. Anschließend können wir das Makro „va_arg“ verwenden, um das Argument abzurufen, auf das der Zeiger „va_list“ zeigt, und den Zeiger an die Position des nächsten Arguments zu verschieben. Verwenden Sie abschließend das Makro va_end, um die Variable va_list zu bereinigen.

Auf diese Weise können wir die Parameterliste in der Variadic-Funktion durchlaufen und nach Bedarf arbeiten.

Kuhong

Hier sind die in der Header-Datei <stdarg.h> definierten Makros und ihre Beschreibungen:

  • void va_start(va_list ap, last_arg): Dieses Makro wird verwendet, um die Variable ap vom Typ va_list zu initialisieren und sie auf den ersten Variablenparameter in der Parameterliste zu verweisen. last_arg ist das letzte bekannte feste Argument, das an die Funktion übergeben wurde, das vor den Auslassungspunkten.
  • type va_arg(va_list ap, type): Dieses Makro wird verwendet, um den nächsten Parameter vom Typ type in der Parameterliste abzurufen und den va_list-Zeiger ap an die Position des nächsten Parameters zu verschieben. Der Rückgabewert ist der erhaltene Parameterwert.
  • void va_end(va_list ap): Dieses Makro wird zum Bereinigen der Variablen ap vom Typ va_list verwendet. Dieses Makro muss nach Verwendung der Variablenparameter aufgerufen werden. Wenn va_end nicht vor der Rückkehr von der Funktion aufgerufen wird, ist das Ergebnis undefiniert.

Zusammen können diese Makros die Parameterliste in einer variablen Parameterfunktion durchlaufen und bearbeiten.

Es ist zu beachten, dass va_start und va_end paarweise vorkommen und sich in derselben Funktion befinden müssen. va_arg wird verwendet, um bestimmte Parameterwerte zu erhalten. Jeder Aufruf verschiebt den va_list-Zeiger auf die Position des nächsten Parameters.

Beispiel

#include <stdio.h>
#include <stdarg.h>

// 计算可变参数列表中的整数之和
int sum(int num_args, ...) {
    va_list ap; // 定义一个用于存储可变参数信息的变量
    int sum = 0;
    
    va_start(ap, num_args); // 初始化 va_list 变量,num_args 是最后一个已知的固定参数
    for (int i = 0; i < num_args; i++) {
        int arg = va_arg(ap, int); // 获取下一个整型参数
        sum += arg;
    }
    va_end(ap); // 清理 va_list 变量

    return sum;
}

int main() {
    int result = sum(4, 10, 20, 30, 40); // 调用可变参数函数
    printf("总和是 %d\n", result); // 输出结果

    return 0;
}

Lassen Sie uns das obige Programm kompilieren und ausführen, was zu den folgenden Ergebnissen führt:

总和是 100

Guess you like

Origin blog.csdn.net/m0_74293254/article/details/134550115