エラーの原因をすぐに把握できると、デバッグは格段に速くなります。
C言語のperror()は、直前に起きた失敗に対する人間が読めるメッセージを標準エラー出力に表示する関数です。
本記事では「いつ、どう呼べばよいか」と「表示される内容の意味」を、初心者の方にも分かりやすいサンプルコードとともに丁寧に解説します。
C言語のperror()とは
標準エラー出力(stderr)にエラーメッセージを表示
perror()は、通常の出力先であるstdoutではなくstderrにメッセージを出力します。
これにより、プログラムの正常な出力とエラー出力を分離できます。
たとえばシェルでリダイレクトする際、標準出力は>、標準エラーは2>で別々に扱えます。
つまり、エラーログだけをファイルに保存するといった運用が容易になります。
stderrのバッファリング
多くの処理系ではstderrはバッファリングされず、すぐに画面へ出ます。
一方stdoutは行バッファリングや全バッファリングになることがあり、表示タイミングが遅れる可能性があります。
したがってエラーはstderrへ出すのが実務上も合理的です。
errnoにもとづくメッセージを出す
perror()は、直近のライブラリ関数が設定したerrnoの値に対応するメッセージを表示します。
たとえばfopen()が失敗するとerrnoにENOENT(ファイルが見つからない)などが入り、その番号に応じた文字列が表示されます。
したがって、失敗を検出した直後に呼ぶことが極めて重要です。
別の関数呼び出しを挟むとerrnoが上書きされ、意味の異なるメッセージが出る恐れがあります。
出力フォーマット
perror(s)の出力は、おおむね次の形式です。
sが空でない場合:s: エラーメッセージ\nsがNULLまたは空文字の場合:エラーメッセージ\n
末尾の改行は自動で付与されます。
プレフィックス文字列に改行を付ける必要はありません。
引数s(プレフィックス)の役割
perror()の引数sは、メッセージの先頭に付ける説明文です。
たとえば"fopen"やargv[0](プログラム名)を渡すことで、どの操作で何が失敗したのかを分かりやすくできます。
プレフィックスが不要な場合は""を渡すとメッセージのみを表示できます。
必要なヘッダー<stdio.h>と<errno.h>
perror()はstdio.hで宣言され、エラー番号errnoはerrno.hで定義されています。
以下のようにインクルードします。
#include <stdio.h> // perrorの宣言
#include <errno.h> // errnoとエラー番号(ENOENTなど)
参考としてプロトタイプは次のとおりです。
// 宣言はstdio.h内
void perror(const char *s);
エラーメッセージの言語は実行環境のロケールに依存します。
日本語環境であれば日本語、英語環境であれば英語になることがあります。
perror()の使い方
失敗を検出した直後にperrorを呼ぶ
関数の戻り値で失敗を検出したら、直ちにperror()を呼びます。
間に別のライブラリ関数を挟むとerrnoが変わる可能性があります。
典型的なパターン
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("fopen"); // 直後に呼ぶ
// 必要ならば後続の処理(後始末、return、exitなど)
}
自分の説明文(プレフィックス)を付ける
プレフィックスには、プログラム名や処理内容、対象リソースなどを入れると読みやすくなります。
たとえば"mytool: load_config"のように階層的に書くと、ログ解析がしやすくなります。
プレフィックスにコロンを自分で付ける必要はありません。
perror()が自動的に": "を付けます。
標準出力(stdout)との違いを知る
stdoutとstderrの主な違いは次のとおりです。
| 項目 | stdout | stderr |
|---|---|---|
| 用途 | 通常の結果表示 | エラーや警告の表示 |
| 既定のバッファリング | 行または全バッファリング | 非バッファまたは行バッファリングが多い |
| シェルでのリダイレクト | > file | 2> file |
| 典型例 | printf | perror, fprintf(stderr, …) |
エラーメッセージはstderrに出すことで、次のような運用が可能です。
# 通常出力はout.txtに、エラーはerr.txtに分ける
./app > out.txt 2> err.txt
よく使う場面
ファイル操作fopen()fclose()、ディレクトリ操作、プロセスやソケット関連のシステムコール、環境依存のI/Oなど、失敗時にerrnoが設定される関数の直後で活躍します。
特にファイルが見つからない、許可がない、パスが長すぎるなどの原因はメッセージを見れば一目瞭然です。
サンプルコードと出力例
fopenの失敗をperrorで表示
存在しないファイルを開こうとして、原因をperror()で表示します。
#include <stdio.h>
#include <errno.h>
int main(void) {
// わざと存在しないファイル名を指定して失敗させる
const char *path = "this_file_does_not_exist.txt";
FILE *fp = fopen(path, "r");
if (fp == NULL) {
// 失敗直後にperrorを呼ぶ。プレフィックスとして操作名を入れる。
perror("fopen");
return 1; // 必要に応じてエラー終了
}
// 実際にはここには来ないが、来た場合の後始末
fclose(fp);
return 0;
}
出力例(環境によりメッセージは異なります):
fopen: No such file or directory
プレフィックス"fopen"のあとに": "が自動で付き、続けてエラーメッセージが表示されます。
プレフィックスなしでメッセージのみ表示
プレフィックスを空文字にすると、メッセージだけを出せます。
#include <stdio.h>
#include <errno.h>
int main(void) {
FILE *fp = fopen("this_file_does_not_exist.txt", "r");
if (fp == NULL) {
// プレフィックスなしで純粋なメッセージだけを出力
perror(""); // NULLを渡す実装もあるが、空文字の方が移植性が高い
return 1;
}
fclose(fp);
return 0;
}
No such file or directory
改行は自動で付与されるため、perror()の前後で余分な改行を入れる必要はありません。
成功時はperrorを呼ばない
成功した場合にはperror()を呼びません。
成功時に呼ぶと意味のないメッセージが出たり、以前のエラー状態を表示してしまう可能性があります。
#include <stdio.h>
#include <errno.h>
int main(void) {
// 書き込み用に一時ファイルを作って成功させる
FILE *fp = tmpfile();
if (fp == NULL) {
// 失敗したときだけperrorを呼ぶ
perror("tmpfile");
return 1;
}
// 成功したのでperrorは呼ばない。通常の出力を行う。
puts("tmpfile succeeded"); // stdoutに出力
fclose(fp);
return 0;
}
実行結果(stdout):
tmpfile succeeded
エラーがなければperror()を呼ぶ必要はありません。
成功メッセージはstdoutに出すのが自然です。
よくある間違いと注意点
errnoを上書きする前に呼ぶ
エラー検出直後にperror()を呼ぶのが鉄則です。
次のように別の関数呼び出しを挟むとerrnoが変わってしまうことがあります。
#include <stdio.h>
#include <errno.h>
int main(void) {
FILE *fp = fopen("missing.txt", "r");
if (fp == NULL) {
// 悪い例: ここでprintfなど別の関数を呼ぶとerrnoが変わる可能性がある
printf("opening file...\n"); // これがerrnoを書き換える可能性は低いが、ゼロではない
// 正しい例: まずはperrorを呼ぶ
perror("fopen");
return 1;
}
fclose(fp);
return 0;
}
もし何らかの事情で直後に呼べない場合は、errnoを退避してから後で使います。
int saved_errno = errno; // 直後に退避
// ...中略(別の処理)...
errno = saved_errno; // 戻してからperror
perror("fopen");
perrorは表示だけ
perror()は表示するだけで、プログラムの終了やerrnoのリセットはしません。
必要であれば適切なエラー処理を続けます。
#include <stdio.h>
#include <errno.h>
#include <stdlib.h> // EXIT_FAILURE
int main(void) {
FILE *fp = fopen("missing.txt", "r");
if (fp == NULL) {
perror("fopen"); // 表示だけ
// ログを追加したり、後始末をしたり、終了コードを返したりする
return EXIT_FAILURE; // 必要に応じて呼び出し元へエラーを伝える
}
fclose(fp);
return 0;
}
メッセージの整形や蓄積が必要な場合は、別途ロギング機能を用意するかfprintf(stderr, ...)で自分で整形してください。
改行は自動で付く
perror()は出力の最後に改行を自動付与します。
したがって、次のように二重改行にならないよう注意します。
perror("open"); // "open: メッセージ\n" が出力される
// printf("\n"); // ここで余計な改行を入れない
プレフィックス文字列に末尾のコロンや改行を入れないのが定石です。
たとえば"open: "と書くのではなく"open"だけ渡します。
まとめ
perror()は、直前のエラー原因を人間が理解しやすい文字列でstderrに即座に表示するための基本機能です。
ポイントは次のとおりです。
プレフィックスで文脈を明確にし、失敗直後に呼ぶ、成功時は呼ばない、改行やコロンは自動付与に任せる、です。
標準出力と標準エラーを意識して使い分ければ、ログ解析やユーザーサポートが格段にやりやすくなります。
まずはファイル操作の失敗時にperror()を組み込むところから始め、短時間で原因にたどり着ける開発サイクルを身につけていきましょう。
