首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

C 標準庫 I/O 函數錯誤識別與處理:errno、perror 與 strerror 詳解

     編輯:關於C

前言

在 C 語言開發中,標準庫 I/O 函數(如 fopen、fread、fwrite 等)在執行失敗時,不會直接拋出異常,而是通過返回值指示錯誤,並將具體錯誤原因記錄在全域變數 errno 中。理解如何正確讀取並解釋 errno,是編寫健壯 C 程式的關鍵技能之一。本文將系統介紹 errno 的機制,以及 perror 和 strerror 兩大錯誤報告函數的用法與陷阱。

一、errno:錯誤碼的全域存儲

1.1 什麼是 errno?

errno 是在 <errno.h> 中聲明的整型全域變數(實際上可能是宏,指向執行緒局部存儲)。當系統調用或標準庫函數發生錯誤時,它們會將對應的正整數錯誤碼賦值給 errno。每個錯誤碼都有唯一的符號常數名稱(如 ENOENT、EACCES、ENOMEM 等),具體定義請參考 man 3 errno。

1.2 典型使用場景

FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    // 此時 errno 已被設置,例如 ENOENT (No such file or directory)
    // 處理錯誤...
}

注意: errno 只有在函數「明確表示失敗」(如返回 NULL、-1 或 EOF)時才具有意義。若函數成功返回,errno 的值未定義(可能保留舊值,也可能被清零),切勿以此判斷是否出錯。

二、perror:快速輸出錯誤描述

2.1 函數原型

#include <stdio.h>
void perror(const char *s);

2.2 行為說明

perror 會向標準錯誤輸出(stderr)輸出一行訊息:

  • 先輸入參數 s 指向的字串(通常是程式上下文,如 "Open file data.txt");
  • 接著輸出冒號 : 和空格;
  • 最後根據當前 errno 的值,輸出系統定義的錯誤描述字串(如 No such file or directory);
  • 換行結束。

2.3 程式範例

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

int main(void) {
    FILE *fp = fopen("abcde", "r");
    if (fp == NULL) {
        perror("Open file abcde");  // 輸出:Open file abcde: No such file or directory
        exit(EXIT_FAILURE);
    }
    fclose(fp);
    return 0;
}

2.4 關鍵陷阱:errno 的易變性

errno 是全域共享資源,幾乎所有系統調用和標準庫函數都可能修改它(極少數如 strcpy 除外)。因此,必須在發生錯誤的函數返回後「立即」讀取或打印 errno,中間不能插入任何其他函數調用。

// 錯誤示範
if (fp == NULL) {
    perror("Open file");   // perror 內部可能調用其他函數,導致 errno 被覆蓋
    printf("errno = %d\n", errno);  // 此時 errno 可能已非 fopen 設置的值!
}

正確做法:若需同時打印描述與錯誤碼,先保存 errno:

if (fp == NULL) {
    int saved_errno = errno;   // 立即保存
    perror("Open file");
    fprintf(stderr, "errno = %d\n", saved_errno);
}

三、strerror:獲取錯誤描述字串

3.1 函數原型

#include <string.h>
char *strerror(int errnum);

3.2 特點與用途

  • 根據傳入的錯誤碼 errnum,返回指向靜態儲存區的錯誤描述字串指標。
  • 不打印,只返回字串,適合自定義格式輸出、寫入日誌、或傳遞給 GUI 介面。
  • 某些介面(如 POSIX 執行緒函數 pthread_*)不使用 errno,而是直接返回錯誤碼,此時 perror 無法使用,必須用 strerror。

3.3 程式範例

#include <stdio.h>
#include <string.h>
#include <errno.h>

void log_error(const char *context) {
    int err = errno;                 // 立即保存
    fprintf(stderr, "[ERROR] %s: %s (errno=%d)\n",
            context, strerror(err), err);
}

int main(void) {
    FILE *fp = fopen("missing.txt", "r");
    if (!fp) log_error("fopen missing.txt");
    return 0;
}

四、最佳實踐總結

  1. 檢查返回值:只在函數明確失敗時檢查 errno。
  2. 即時保存:在調用任何其他函數前,先將 errno 存入區域變數。
  3. 選擇工具:簡單診斷用 perror;需格式化、記錄或非 errno 來源錯誤碼用 strerror(或執行緒安全版 strerror_r)。
  4. 提供上下文:傳給 perror 或自行格式化的字串應包含「做什麼操作、操作什麼資源」,便於定位問題。

五、參考資源

  • Linux 手冊頁:man 3 errno、man 3 perror、man 3 strerror
  • POSIX.1-2017 標準:<errno.h>、<string.h>、<stdio.h>
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved