C Error Handling: errno

·

1 min read

最近碰到的坑,但查了下發現是基礎概念,整理記錄一下。

背景: gmtime_r

他會從傳進去的 time_t 算出年月日、星期、時分秒。這並不是件簡單的事情,除了閏年外,假如考慮進時區的話,還要計入日光節約時間,會需要讀取一些定期更新的檔案。

gmtime_r 的細節不是這文章的重點,只在這裡大概說一下它的功能。

使用並且處理

#include <ctime>
#include <cstdio>

#include <errno.h>

int main() {
    time_t tt = 0;

    std::tm tm0;
    errno = 0;
    gmtime_r(&tt, &tm0);
    if (errno != 0) {
        perror("gmtime_r");
    }
    return 0;
}

這在 docker ubuntu 16.04 上會遇到:

gmtime_r: No such file or directory

但是在非 docker 的 ubuntu 16.04 或者 docker ubuntu 20.04 或者 MacOS。

一開始,我以爲可能是類似 timedatectl 的問題,因為他也有類似的現象:

root@618d495a44a6:~# timedatectl status
Failed to create bus connection: No such file or directory

在中間過程他似乎會去尋找某個檔案。雖然這有點亂槍打鳥,但有沒有可能他們用到相同的檔案?

所以我嘗試了這個做法,雖然 timedatectl 可行,但最上面的程式仍然會噴錯誤。

errno 的原則和 gmtime_r 的文件

後來,我重新翻了 APUE 的最前面,關於 errno 的必需要知道的事情,才發現我大概錯誤的使用了 errno:

  1. 函數並不會主動清乾淨 errno

  2. 只有在函數告訴你有錯(例如回傳的指標 spec 上說明錯誤會是 NULL),才可以去檢查 errno (或者說,這時 errno 才有意義)

也就是說,有可能一個系統函數設計這樣:讀取某個固定路徑的檔案,假如該檔案沒有,就用其他預設值,不需要回傳錯誤。

在沒有該檔案時, errno 會被 open 設置,但是函數並不會在回傳預設值前清乾淨 errno。

最後,根據 gmtime_r文件,直接判斷回傳指標是否為 NULL 才是正確的作法。

#include <ctime>
#include <cstdio>

#include <errno.h>

int main() {
    time_t tt = 0;
    std::tm tm0;
    if (!gmtime_r(&tt, &tm0)) {
        perror("gmtime_r");
    }
    return 0;
}