C言語でシステムコールや標準ライブラリ関数を使うとき、成功・失敗の判定だけでなく、なぜ失敗したのかという原因を知ることが重要です。
そのときに活躍するのがerrnoです。
本記事では、errnoの基本的な考え方から、正しい使い方、エラーメッセージの取得方法、注意点まで、実用的な観点から詳しく解説します。
errnoとは何か
errnoの役割と位置づけ
errnoは、ライブラリ関数やシステムコールのエラー原因を表す整数の「変数」です。
多くの標準ライブラリ関数は、エラー発生時にこのerrnoにエラー内容を示す番号を設定します。
具体的には次のような特徴があります。
- C標準では
errnoはint型の変数として定義されています。 - C言語のヘッダ
<errno.h>で宣言されます。 - 関数が失敗したあとにerrnoを見ることで、失敗の具体的な理由を判断できます。
errnoが更新されるタイミング
多くの関数はエラー発生時にerrnoを書き換えますが、次の点に注意が必要です。
- 成功した関数はerrnoを0に戻すとは限りません。
- 関数呼び出しに失敗した後、次の別の関数呼び出しを行うと、errnoが上書きされる可能性があります。
- そのためエラー原因を知りたいときは、エラーを返した直後のタイミングでerrnoを参照することが重要です。

このようにerrnoは、戻り値だけでは分からない「なぜ失敗したか」を教えてくれる補助情報として機能します。
errnoを使うためのヘッダファイル
errnoを使うには、プログラムの先頭で次のヘッダをインクルードします。
#include <errno.h> /* errno本体とエラー番号の定義 */
#include <string.h> /* strerror関数(エラーメッセージ取得) */
#include <stdio.h> /* printfなど */
<errno.h>には、errno変数の宣言と、エラー番号のマクロ定義(例: ENOENT, EACCESなど)が含まれます。
errnoの基本的な使い方
典型的な利用パターン
errnoを用いたエラー処理の典型的な流れは次のようになります。
- errnoを0に初期化する(必要な場合)。
- 標準ライブラリ関数・システムコールを呼び出す。
- 戻り値で成功・失敗を判定する。
- 失敗していたらerrnoを確認し、エラー番号に応じて処理分岐やメッセージ出力を行う。

fopenとerrnoの基本例
ファイルを開くfopenとerrnoを組み合わせた、最も基本的なサンプルを示します。
#include <stdio.h>
#include <errno.h> /* errnoとエラー番号 */
#include <string.h> /* strerror関数 */
int main(void) {
/* 存在しないファイル名をあえて指定する */
const char *filename = "not_exists.txt";
/* 念のためerrnoを0に初期化しておく */
errno = 0;
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
/* fopenが失敗したのでerrnoを確認する */
printf("ファイルを開けませんでした: %s\n", filename);
/* errnoの数値を表示 */
printf("errno = %d\n", errno);
/* strerrorで人間向けメッセージに変換 */
printf("エラーメッセージ: %s\n", strerror(errno));
return 1;
}
/* ここに通常の処理を書く(今回はすぐ閉じるだけ) */
fclose(fp);
return 0;
}
ファイルを開けませんでした: not_exists.txt
errno = 2
エラーメッセージ: No such file or directory
この例では、ファイルが存在しないためfopenが失敗し、errnoにENOENT(2)が設定されます。
errnoは数値そのものではなく、ENOENTのようなマクロ名で扱うのが基本です。
errnoのエラー番号マクロ
errnoに格納される値は、多くの場合、定義済みマクロ(定数)で表現されます。
たとえばファイル関連でよく登場するものには次があります。
| マクロ名 | 代表的な意味 |
|---|---|
| ENOENT | ファイルやディレクトリが存在しない |
| EACCES | アクセス権限がない |
| EEXIST | すでに同名のファイルが存在する |
| ENOMEM | メモリ不足 |
| EINVAL | 無効な引数(値)が渡された |
C標準で必須なのはごく一部ですが、UNIX系システムではさらに多くのエラー番号が定義されています。
strerrorとperrorでメッセージを取得する
strerrorを使って文字列に変換する
errnoは単なる数値なので、そのままでは意味がわかりにくいです。
そこでerrnoを人間向けのエラーメッセージ文字列に変換する関数がstrerrorです。
#include <string.h>
char *strerror(int errnum);
- 引数
errnumにerrnoの値を渡します。 - 戻り値としてエラー内容を示す文字列へのポインタを返します。
- この文字列は静的領域にあり、プログラム側でfreeなどしてはいけません。
もう少し実践的な例を見てみます。
#include <stdio.h>
#include <errno.h>
#include <string.h>
void print_error_message(const char *func_name) {
/* errnoの値に対応するエラーメッセージを取得 */
const char *msg = strerror(errno);
/* 関数名とあわせて表示する */
printf("%sでエラーが発生しました: %s (errno=%d)\n",
func_name, msg, errno);
}
int main(void) {
errno = 0;
FILE *fp = fopen("no_such_file.txt", "r");
if (fp == NULL) {
print_error_message("fopen");
return 1;
}
fclose(fp);
return 0;
}
fopenでエラーが発生しました: No such file or directory (errno=2)
perrorを使って簡単に出力する
もっと簡単に、標準エラー出力にメッセージを表示したい場合はperrorを使います。
#include <stdio.h>
void perror(const char *s);
- 引数
sに、エラーの前に付けたい文字列(プレフィックス)を渡します。 - 出力形式は
"s: メッセージ\n"のようになります。 - errnoの値を内部で参照し、対応するメッセージを出力します。
#include <stdio.h>
#include <errno.h>
int main(void) {
FILE *fp = fopen("no_such_file.txt", "r");
if (fp == NULL) {
/* "fopen"というプレフィックスを付けてエラーを表示 */
perror("fopen");
return 1;
}
fclose(fp);
return 0;
}
fopen: No such file or directory

strerrorはメッセージを「取得する」関数、perrorはメッセージを「表示する」関数と覚えると整理しやすいです。
errnoを使うときの注意点
errnoをチェックする前に戻り値を確認する
最も重要な注意点は、「戻り値がエラーであることを確認してからerrnoを見る」ことです。
多くのライブラリ関数は、成功時にはerrnoを変更しないことがあります。
そのため、「処理は成功したのに、過去のエラー値がerrnoに残っている」という状況がありえます。
#include <stdio.h>
#include <errno.h>
int main(void) {
errno = 0;
/* 成功するかもしれない処理 */
FILE *fp = fopen("existing.txt", "r");
/* いきなりerrnoだけを見るのは危険 */
printf("errno = %d\n", errno);
if (fp == NULL) {
perror("fopen");
return 1;
}
fclose(fp);
return 0;
}
このような書き方だと、fopenが成功しているのに、過去に別の処理で発生したエラー値が残っていて、誤解を生む可能性があります。
必ず戻り値で成功・失敗を判定してからerrnoを用いるようにします。
errnoはスレッドローカル(実装依存)
POSIX環境や多くの実装では、errnoはスレッドごとに独立した値(スレッドローカル)として扱われます。
そのため、別スレッドのエラーが自分のスレッドのerrnoに影響を与えることはありません。
ただし、以下の点に注意してください。
- マルチスレッド環境では
strerrorの代わりにstrerror_rなどのスレッドセーフな関数を使うことが推奨されるケースがあります。 - C標準だけではスレッドとの関係は定義されておらず、POSIXなどの仕様に依存します。
この記事の範囲では、「マルチスレッドでも基本的にはerrnoはスレッドごとに独立している」と理解しておけば十分です。
errnoを勝手に上書きしない
ふつう、アプリケーション側がerrnoに任意の値を入れる必要はほとんどありません。
errnoはライブラリ関数やシステムコールが設定するためのものだからです。
ただし、次の目的で一時的に0を代入することはあります。
- 「この関数呼び出しで本当にエラーが起きたか」をはっきりさせたい場合に、事前に
errno = 0;としておき、その関数の後でerrno != 0かどうかを確認する。
それ以外の用途でerrnoに任意の整数を代入するのは避けるべきです。
errnoは「結果」としてセットされるべきもので、「指定」する対象ではないと考えるとよいです。
errnoを使った実用的なエラー処理例
ファイル読み込み関数でerrnoを返すパターン
現実的なコードでは、「一つの関数の中でファイルを開く→読む→閉じる」という処理をまとめ、失敗した場合にerrnoの値を呼び出し元に返す、といったパターンがよく使われます。

#include <stdio.h>
#include <errno.h>
#include <string.h>
int read_file(const char *filename) {
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
/* fopenが設定したerrnoをそのまま返す */
return errno;
}
/* ここでファイル読み込み処理を行う(サンプルのため省略) */
if (fclose(fp) != 0) {
/* fcloseもerrnoを設定する場合がある */
return errno;
}
/* 正常終了 */
return 0;
}
int main(void) {
const char *filename = "sample.txt";
int err = read_file(filename);
if (err != 0) {
/* ここでerrnoではなく、関数から返ってきたerrを使うのがポイント */
printf("ファイル処理中にエラーが発生しました: %s\n", filename);
printf("エラーコード: %d\n", err);
printf("内容: %s\n", strerror(err));
return 1;
}
printf("ファイル処理は正常に完了しました。\n");
return 0;
}
ファイル処理中にエラーが発生しました: sample.txt
エラーコード: 2
内容: No such file or directory
このように、内部で発生したerrnoを「intの戻り値」として外側に伝えると、ライブラリ的な関数が作りやすくなります。
errnoに応じて処理を分岐する
errnoの値に応じて、処理の分岐を実装することもよくあります。
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void) {
const char *filename = "test.txt";
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
/* errnoに応じて分岐 */
switch (errno) {
case ENOENT:
printf("ファイルが存在しません: %s\n", filename);
break;
case EACCES:
printf("ファイルへのアクセス権がありません: %s\n", filename);
break;
default:
printf("ファイルを開けませんでした(%d): %s\n",
errno, strerror(errno));
break;
}
return 1;
}
printf("ファイルを開くことに成功しました。\n");
fclose(fp);
return 0;
}
ファイルが存在しません: test.txt
このような分岐を行うと、エラー内容に応じてユーザーへの案内やリトライ戦略を変えることができます。
まとめ
errnoは、C言語でのエラー処理において「なぜ失敗したか」を知るための中心的な仕組みです。
関数の戻り値で成功・失敗を判定し、失敗時にはerrnoを参照してstrerrorやperrorでメッセージ化する、という流れを押さえておけば、多くのエラー状況に対応できます。
注意点としては、戻り値を確認せずにerrnoだけを見ることを避けること、エラーが発生した直後にerrnoを使うこと、そして必要に応じてerrnoの値を呼び出し元に伝える設計を行うことが挙げられます。
これらを意識することで、原因が追いやすく、ユーザーにとっても分かりやすいエラー処理をC言語で実現できます。
