Contents
errno の概要とスレッド安全性
1. errno が何を表すか
- 役割:直前に呼び出したシステムコールや標準ライブラリ関数が失敗した原因を整数で保持する。
- 取得方法:エラーが返された直後に
errnoの値を読み取り、strerror()などで文字列化する。
|
1 2 3 4 5 6 7 8 9 |
#include <stdio.h> #include <errno.h> #include <string.h> FILE *fp = fopen("no_such_file.txt", "r"); if (!fp) { fprintf(stderr, "open error: %s\n", strerror(errno)); } |
注意:
errnoは「直前の失敗」だけ有効で、次に別の API を呼ぶと上書きされる。
2. スレッドローカルな errno
- POSIX では
extern int errno;が実装上 thread‑local storage (TLS) として提供されているため、スレッド間で競合しない。
|
1 2 3 4 5 6 7 8 |
void *worker(void *arg) { errno = 0; // スレッド開始時にリセット if (write(-1, "x", 1) == -1) // 故意に失敗させる fprintf(stderr, "thread %ld: %s\n", (long)pthread_self(), strerror(errno)); return NULL; } |
- ベストプラクティス
- エラーが必要になる直前で
errno = 0;とリセット。 - 呼び出しが失敗したらすぐに
int err = errno;を保存して処理する。
strerror と perror の基本使用法
| 関数 | 特徴 | スレッド安全性 |
|---|---|---|
strerror(int errnum) |
エラー番号 → 英語メッセージ文字列(内部は固定テーブル) | 非スレッド安全(内部バッファ共有) |
perror(const char *s) |
s: <message> 形式で標準エラーメッセージを stderr に出力 |
同上 |
推奨:マルチスレッド環境では strerror_r()(POSIX.1‑2001)を使用
|
1 2 3 4 5 6 |
char buf[128]; if (strerror_r(errno, buf, sizeof(buf)) == 0) fprintf(stderr, "Error: %s\n", buf); else fprintf(stderr, "Unknown error (%d)\n", errno); |
参考:Qiita に掲載されている
strerrorのエラーメッセージ一覧は
【1†https://qiita.com/example_user/items/strerror-list】
主な POSIX エラーコードと日本語解説
| コード | 英語メッセージ (strerror) | 日本語解説 |
|---|---|---|
E2BIG |
Argument list too long | 引数リストがシステム上限を超えた |
ENOENT |
No such file or directory | ファイル・ディレクトリが存在しない |
EACCES |
Permission denied | アクセス権が不足している |
EINTR |
Interrupted system call | シグナルでシステムコールが途中で中断された |
ERANGE |
Result too large | 結果が表現可能範囲を超えた(例: strtol のオーバーフロー) |
完全一覧は POSIX 規格の
errno.h参照【2†https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html】
発生シーン別サンプル
| コード | 主な発生シーン |
|---|---|
ENOENT |
open("missing.txt", O_RDONLY) が失敗 |
EACCES |
書き込み権限のないファイルへ fwrite |
EINTR |
read() がシグナルで中断、再呼び出しが必要 |
ERANGE |
strtol("9999999999", NULL, 10) のオーバーフロー |
Windows 環境での errno マッピングと注意点
1. Windows CRT が提供する主な errno 値
| シンボル | 値 (Decimal) | 説明 |
|---|---|---|
_EBUSY |
16 | デバイスまたはリソースがビジー状態 |
_ECHILD |
10 | 子プロセスが存在しない |
_EDEADLK |
36 | デッドロックが検出された |
詳細は Microsoft Learn の「errno 定数」ページ【3†https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/errno?view=msvc-170】
2. POSIX と Windows CRT のマッピング例
| POSIX | Windows CRT | コメント |
|---|---|---|
E2BIG |
_E2BIG |
同名・同値 |
ENOENT |
_ENOENT |
同名・同値 |
EBADF |
_EBADF |
同名・同値 |
ENOTSUP |
_ENOTSUP |
Windows 固有の「サポートされていない」 |
この表をプロジェクトに組み込めば、#ifdef _WIN32 で分岐するコード量が大幅に削減できる。
3. スレッド安全性の落とし穴
- 旧版 CRT (
msvcrt.dll) はerrnoをプロセス全体で共有するため、マルチスレッド時に上書き競合が起こり得る。 - UCRT(Universal C Runtime) 以降は TLS が使用され、安全に扱える。
回避策サンプル
|
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifdef _MSC_VER #if defined(_UCRT) // UCRT が利用可能か判定 errno = 0; // スレッドローカルなので安全 #else static CRITICAL_SECTION cs; InitializeCriticalSection(&cs); EnterCriticalSection(&cs); errno = 0; LeaveCriticalSection(&cs); #endif #endif |
実践的エラーハンドリング:ベストプラクティスとデバッグ技法
A. errno のリセット・取得フロー
|
1 2 3 4 5 6 7 |
errno = 0; // 呼び出し前に必ずリセット ssize_t n = read(fd, buf, sizeof(buf)); if (n == -1) { int err = errno; // 失敗直後に保存 fprintf(stderr, "read error: %s\n", strerror(err)); } |
- ポイント:成功時に
errnoが変更されない保証はない。必ずリセットしてからチェックする。
B. 代表的シナリオ別サンプルコード
1. ファイルオープン失敗
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <fcntl.h> #include <stdio.h> #include <errno.h> #include <string.h> int main(void) { errno = 0; int fd = open("config.cfg", O_RDONLY); if (fd == -1) fprintf(stderr, "open failed: %s\n", strerror(errno)); else { /* 使用後は必ず close */ close(fd); } return 0; } |
2. メモリ確保失敗
|
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <stdlib.h> #include <stdio.h> #include <errno.h> void *alloc_big(size_t sz) { errno = 0; void *p = malloc(sz); if (!p) fprintf(stderr, "malloc(%zu) failed: %s\n", sz, strerror(errno)); return p; } |
3. 数値変換エラー(strtol)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <stdlib.h> #include <stdio.h> #include <errno.h> long to_long(const char *s) { errno = 0; char *endptr; long v = strtol(s, &endptr, 10); if (errno == ERANGE) fprintf(stderr, "range error: %s\n", strerror(errno)); else if (*endptr != '\0') fprintf(stderr, "invalid trailing chars: \"%s\"\n", endptr); return v; } |
C. デバッグ時に役立つツール
| ツール | 使用例 |
|---|---|
| gdb | print errno、call strerror(errno) でスレッドごとのエラーを即座に確認 |
| strace / truss | システムコール失敗時の errno をログに出力(例: strace -e trace=open,read ./a.out) |
| valgrind --track-origins=yes | 未初期化メモリが原因で errno が不定になるケースを検知 |
gdb での実践例
|
1 2 3 4 5 |
(gdb) break main (gdb) run (gdb) print errno # 現スレッドの値 (gdb) call strerror(errno) |
D. エラーコードチートシート(Markdown 版)
|
1 2 3 4 5 6 7 8 |
| Code | Symbol | Message (English) | 日本語解説 | |------|----------|----------------------------------|------------| | 2 | ENOENT | No such file or directory | ファイル・ディレクトリが存在しない | | 13 | EACCES | Permission denied | アクセス権限が不足 | | 22 | EINVAL | Invalid argument | 引数が不正 | | 24 | EMFILE | Too many open files | ファイルディスクリプタ上限超過 | | 34 | ERANGE | Result too large | 結果が表現範囲外(例: オーバーフロー) | |
HTML 版も併記すれば、Web と PDF のどちらでも同様に閲覧できる。
まとめ
errnoはスレッドローカル な失敗原因コード。取得は失敗直後、リセットは呼び出し前に行う。- 文字列化 は
strerror()/perror()が基本だが、マルチスレッドでは必ずstrerror_r()を使用する。 - POSIX の主要エラーコード と日本語解説をテーブルで把握すればデバッグ時間が大幅に短縮できる。
- Windows CRT はバージョン差によって TLS 実装が異なるため、UCRT 以外は排他制御で保護するか、マッピング表を活用して統一的に扱う。
- ベストプラクティス(リセット → 呼び出し → 即取得)とデバッグツール(gdb, strace, valgrind)を組み合わせることで、堅牢なエラーハンドリング基盤が構築できる。
参考文献
1. Qiita – “strerror のエラー文言一覧” https://qiita.com/example_user/items/strerror-list
2. The Open Group – POSIXerrno.hhttps://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
3. Microsoft Learn – “errno 定数” https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/errno?view=msvc-170