• C 标准库 - <signal.h>和<stdarg.h>详解


    目录

    简介

    库变量

    库宏

    库函数

    实例

    简介

    库变量

    库宏

    实例


    简介

    是 C 语言标准库中的头文件之一,提供了对信号处理的支持。在 Unix 和类 Unix 系统中,信号是一种进程间通信机制,用于在进程之间传递异步事件的信息,例如错误、异常、中断等。 头文件定义了处理信号的相关函数和宏。

    使用 头文件时需要注意以下几点:

    • 信号处理函数应尽可能地简单,避免进行复杂或耗时的操作,因为它们通常是在不同上下文中执行的。
    • 信号处理函数应尽可能地避免调用不可重入函数,因为它们可能会被信号中断。
    • 一些信号是不可靠的,即不能保证它们是否会丢失。因此,在处理这些信号时需要格外小心。

    总的来说, 提供了 C 语言中处理信号的基本机制,能够帮助程序员编写处理异步事件的代码。然而,由于信号处理涉及到并发和异步事件,因此编写可靠的信号处理代码是比较困难的,需要谨慎对待。

    库变量

    下面是头文件 signal.h 中定义的变量类型:

    头文件中定义了 sig_atomic_t 类型,用作信号处理程序中的变量类型。sig_atomic_t 是一个整数类型,在信号处理程序中可以作为原子操作的变量来访问。

    这意味着,即使在处理异步信号的情况下,对 sig_atomic_t 类型的变量进行读取和写入操作都是原子的。原子操作是不可中断的操作,要么完全执行,要么完全不执行,不会被其他并发操作打断。这是确保在信号处理程序中对变量的访问是安全的关键。

    sig_atomic_t 类型通常用于在信号处理程序中保存状态信息或标志,以指示信号的发生或某种事件的状态。由于它是原子的,可以保证对于多个并发的信号处理程序,对该类型变量的读取和写入不会出现竞态条件(Race Condition)导致的错误。

    需要注意的是,sig_atomic_t 只保证对单个变量的原子性操作,而不是对于多个变量之间的原子操作。

    总而言之,sig_atomic_t 类型是用于在信号处理程序中进行原子操作的变量类型,可以提供一定程度的线程安全性。

    库宏

    头文件中定义了一些宏,这些宏在处理信号时会使用到。以下是其中的一些常用宏:

    • SIG_DFL:默认的信号处理程序。当信号的处理程序被设置为 SIG_DFL 时,表示使用系统默认的处理方式,通常是终止程序或忽略信号。
    • SIG_ERR:表示一个信号错误。当某个函数返回 SIG_ERR 时,表示出现了信号处理错误。
    • SIG_IGN:忽视信号。设置信号处理程序为 SIG_IGN 表示忽略该信号,即不做任何处理。

    除了上述宏之外, 还定义了一系列以 SIG 开头的宏,用于表示各种条件下的信号码,例如:

    • SIGABRT:表示程序异常终止。
    • SIGFPE:表示算术运算出错,如除数为 0 或溢出。
    • SIGILL:表示非法函数映象,如非法指令。
    • SIGINT:表示中断信号,通常由用户按下 ctrl-C 产生。
    • SIGSEGV:表示非法访问存储器,如访问不存在的内存单元。
    • SIGTERM:表示发送给程序的终止请求信号。

    这些宏可以与 signal 函数一起使用,用于指定信号的功能,例如设置信号处理程序、忽略信号或使用默认处理程序。

    需要注意的是,这些宏的具体实现和行为可能会因操作系统和编译器的不同而有所差异。

    库函数

    头文件中定义了用于设置和处理信号的函数。其中两个常用函数如下:

    • void (*signal(int sig, void (*func)(int)))(int):该函数用于设置一个函数来处理指定的信号 sig,即信号处理程序。func 参数是一个指向函数的指针,该函数表示在接收到信号时要执行的操作。如果 func 参数为 SIG_DFL,则表示使用系统默认的处理方式;如果为 SIG_IGN,则表示忽略该信号。该函数返回值为之前注册的信号处理程序的地址。
    • int raise(int sig):该函数用于发送指定的信号 sig 给当前进程。如果信号成功发送,则返回 0,否则返回非 0 值。该函数可以用于测试信号处理程序是否正确安装或测试信号是否被正确处理。

    需要注意的是,虽然 signal 函数在大多数情况下可以用于注册信号处理程序,但是在一些特定的平台上可能存在不同的实现方式。为了编写可移植的代码,建议查阅特定平台的文档或参考相应的系统调用。

    另外,由于信号处理程序通常在异步的上下文中执行,因此在编写信号处理程序时需要特别小心,避免出现竞态条件或其他不确定的行为。常见的编写技巧包括尽量使用原子操作、避免使用非可重入函数、不在信号处理程序中分配内存等。

    实例

    1. #include
    2. #include
    3. #include
    4. #include
    5. /* 信号处理程序 */
    6. void sigint_handler(int sig) {
    7. printf("接收到中断信号 %d\n", sig);
    8. }
    9. int main() {
    10. /* 注册 SIGINT 信号处理程序 */
    11. if (signal(SIGINT, sigint_handler) == SIG_ERR) {
    12. perror("无法注册 SIGINT 信号处理程序");
    13. exit(EXIT_FAILURE);
    14. }
    15. /* 发送 SIGINT 信号 */
    16. printf("发送中断信号...\n");
    17. if (raise(SIGINT) != 0) {
    18. perror("无法发送 SIGINT 信号");
    19. exit(EXIT_FAILURE);
    20. }
    21. printf("程序正常结束。\n");
    22. exit(EXIT_SUCCESS);
    23. }

    在上面的代码中,我们先定义了一个名为 sigint_handler 的函数,用于处理接收到的 SIGINT 信号。然后在 main 函数中,我们使用 signal 函数将 SIGINT 信号的处理程序设置为 sigint_handler。接着使用 raise 函数向进程发送 SIGINT 信号,并在控制台输出相应的信息。最后程序正常结束。 

    让我们编译并运行上面的程序,这将产生以下结果:

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

    简介

    stdarg.h 头文件定义了一个变量类型 va_list 和三个宏,这三个宏可用于在参数个数未知(即参数个数可变)时获取函数中的参数。 可变参数的函数通在参数列表的末尾是使用省略号(,...)定义的。

    库变量

    va_list 是 头文件中定义的变量类型。

    va_list 类型是一个用于存储可变参数信息的数据类型。通常情况下,我们会定义一个 va_list 类型的变量来操作可变参数函数。

    具体使用时,通过调用 va_start 宏来初始化 va_list 变量,将其指向参数列表的第一个参数。然后,我们可以使用 va_arg 宏来获取 va_list 指针所指向的参数,并将指针移动到下一个参数的位置。最后,使用 va_end 宏来清理 va_list 变量。

    这样,我们就可以在可变参数函数中遍历参数列表,根据需要进行操作。

    库宏

    下面是 头文件中定义的宏及其描述:

    • void va_start(va_list ap, last_arg):该宏用于初始化 va_list 类型的变量 ap,将其指向参数列表中的第一个可变参数。last_arg 是最后一个传递给函数的已知固定参数,即省略号之前的参数。
    • type va_arg(va_list ap, type):该宏用于获取参数列表中类型为 type 的下一个参数,并将 va_list 指针 ap 移动到下一个参数的位置。返回值为获取到的参数值。
    • void va_end(va_list ap):该宏用于清理 va_list 类型的变量 ap,在使用完可变参数之后必须调用该宏。如果在从函数返回之前没有调用 va_end,则结果是未定义的。

    这些宏一起配合使用,可以实现对可变参数函数中参数列表的遍历和操作。

    需要注意的是,va_start 和 va_end 必须成对出现,且位于同一个函数中。va_arg 则用于获取具体的参数值,每次调用会将 va_list 指针移动到下一个参数的位置。

    实例

    1. #include
    2. #include
    3. // 计算可变参数列表中的整数之和
    4. int sum(int num_args, ...) {
    5. va_list ap; // 定义一个用于存储可变参数信息的变量
    6. int sum = 0;
    7. va_start(ap, num_args); // 初始化 va_list 变量,num_args 是最后一个已知的固定参数
    8. for (int i = 0; i < num_args; i++) {
    9. int arg = va_arg(ap, int); // 获取下一个整型参数
    10. sum += arg;
    11. }
    12. va_end(ap); // 清理 va_list 变量
    13. return sum;
    14. }
    15. int main() {
    16. int result = sum(4, 10, 20, 30, 40); // 调用可变参数函数
    17. printf("总和是 %d\n", result); // 输出结果
    18. return 0;
    19. }

    让我们编译并运行上面的程序,这将产生以下结果:

    总和是 100
    
  • 相关阅读:
    Ubuntu18.04配置Cube-SLAM
    Ubuntu安装Python 3.10
    快速入门 git 代码版本管理工具(04)
    LeetCode-45-跳跃游戏Ⅱ-贪心算法
    变电站电源屏及温湿度和烟感设备协议接入流程记录
    服务器之日常整活
    docker数据卷和数据卷容器
    【C语言】动态内存管理
    2022牛客多校六 M-Z-Game on grid(动态规划)
    记一个JSON返回数据的bug
  • 原文地址:https://blog.csdn.net/m0_74293254/article/details/134550115