跳转至

异常处理机制

POSIX 异常处理机制总结


一、核心机制

POSIX 异常处理主要依赖以下机制管理运行时错误和信号:


1. 错误返回与 errno

  • 错误标识
    POSIX 系统调用和库函数通过返回值指示错误(通常返回 -1NULL),并通过全局变量 errno 存储具体错误码。

    • 示例:
      int fd = open("file.txt", O_RDONLY);
      if (fd == -1) {
          // open 失败,errno 被设置为具体错误码(如 EACCES、ENOENT)
          perror("open failed");
      }
      
  • errno 的特性

    • 线程安全:现代实现中,errno 是线程局部变量(每个线程独立维护)。
    • 易失性:函数调用可能覆盖 errno,需在失败后立即保存其值。

2. 错误处理函数

  • perror()
    根据当前 errno 输出可读错误信息到 stderr

    if (write(fd, buf, size) == -1) {
        perror("write failed"); // 输出:write failed: Permission denied
    }
    

  • strerror()
    将错误码转换为字符串,适合自定义日志格式。

    #include <string.h>
    if (mkdir("/root/dir", 0755) == -1) {
        printf("Error: %s\n", strerror(errno)); // 输出:Error: Permission denied
    }
    


3. 信号(Signals)

信号是异步异常通知机制,用于处理进程外部事件(如 SIGSEGVSIGINT)。

  • 信号处理函数

    • signal():简单注册信号处理函数(不推荐,兼容性差)。

      void handler(int sig) {
          printf("Received signal: %d\n", sig);
      }
      signal(SIGINT, handler); // 捕获 Ctrl+C
      

    • sigaction():更安全的信号处理方式,支持精细控制。

      struct sigaction sa;
      sa.sa_handler = handler;
      sigemptyset(&sa.sa_mask);
      sa.sa_flags = 0;
      sigaction(SIGTERM, &sa, NULL); // 捕获终止信号
      

  • 常见信号类型

信号 含义 默认行为
SIGSEGV 非法内存访问(段错误) 终止进程
SIGFPE 算术异常(如除零) 终止进程
SIGINT 终端中断(Ctrl+C) 终止进程
SIGPIPE 管道或Socket写入端关闭 终止进程
SIGCHLD 子进程状态变化 忽略
  • 信号处理注意事项
    • 信号处理函数应尽量简单,避免调用非异步安全函数(如 printfmalloc)。
    • 使用 volatile sig_atomic_t 类型标记异步事件。

4. 资源错误处理

  • 动态内存分配

    int *arr = malloc(100 * sizeof(int));
    if (arr == NULL) {
        // 内存分配失败
        fprintf(stderr, "malloc failed: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    

  • 文件与IO错误

    • 文件不存在(ENOENT)、权限不足(EACCES)、设备已满(ENOSPC)等。
    • 示例:
      FILE *fp = fopen("data.txt", "r");
      if (fp == NULL) {
          // 文件打开失败,检查 errno
          if (errno == ENOENT) {
              fprintf(stderr, "File not found.\n");
          }
      }
      

5. 线程错误处理

POSIX 线程(pthread)函数通过返回值传递错误码(而非设置 errno)。
- 示例

pthread_t thread;
int ret = pthread_create(&thread, NULL, worker_func, NULL);
if (ret != 0) {
    // 错误码在返回值中(如 EAGAIN、EINVAL)
    fprintf(stderr, "Thread creation failed: %s\n", strerror(ret));
}


二、最佳实践

  1. 严格检查返回值
    对所有可能失败的函数调用检查返回值(尤其是系统调用和库函数)。

  2. 及时处理 errno

    • 在函数失败后立即保存 errno,避免被后续调用覆盖。
    • 使用 perror()strerror() 输出错误信息。
  3. 信号处理安全

    • 使用 sigaction 替代 signal
    • 避免在信号处理函数中执行复杂操作。
  4. 资源清理
    确保在错误退出前释放已分配的资源(如关闭文件描述符、释放内存)。

  5. 错误码分类处理
    根据 errno 值选择重试、回退或终止程序。

    if (connect(sockfd, addr, addrlen) == -1) {
        if (errno == EINTR) {
            // 被信号中断,可重试
        } else {
            perror("connect failed");
            close(sockfd);
            exit(EXIT_FAILURE);
        }
    }
    


三、总结

POSIX 异常处理的核心是通过 返回值 + errno 标识错误,结合 信号 处理异步事件。关键要点:

  • 系统调用错误依赖 errno,线程错误通过返回值传递。
  • 使用 perror/strerror 转换错误码为可读信息。
  • 信号处理需保证安全性和简单性。
  • 资源错误需及时清理,避免泄漏。

通过合理利用这些机制,可以构建健壮且可维护的系统程序。