C言語でシステムコールやライブラリ関数が失敗したとき、原因が分からず困ることは少なくありません。
そんなときに役立つのがperror関数です。
perrorは、直前に発生したエラー内容を、人間が読めるメッセージとして表示してくれる関数です。
本記事では、perrorの基本構文から、実際の使い方、注意点までを、サンプルコード付きで分かりやすく解説します。
C言語のperror関数とは
perror関数の役割

C言語のperror関数は、標準ライブラリが提供するエラー表示専用の関数です。
主な役割は次の1点に集約されます。
直前に発生したエラー番号(errno)に対応するメッセージを、標準エラー出力(stderr)に表示することです。
多くのシステムコールや標準ライブラリ関数は、失敗したときにerrnoというグローバル変数にエラー番号を設定します。
perrorは、このerrnoの値をもとに、対応するエラーメッセージを表示します。
たとえば、存在しないファイルをfopenで開こうとして失敗した場合、errnoにはENOENT(No such file or directory)が設定され、perrorを呼び出すと、その旨をメッセージとして出力してくれます。
どのヘッダファイルで定義されているか
perror関数を使うには、次のヘッダファイルをインクルードします。
#include <stdio.h>… perror本体の宣言#include <errno.h>… errnoの宣言およびエラー番号の定義
一般的には、エラー処理を行うプログラムでは<stdio.h>と<errno.h>をセットでインクルードすることが多いです。
perror関数の基本構文
関数プロトタイプと戻り値
perrorの宣言は、標準的な処理系では次のようになっています。
void perror(const char *s);
ポイントは次の通りです。
- 戻り値は
void… 成功・失敗という概念はなく、単にメッセージを出力するだけです。 - 引数は
const char *s… メッセージの先頭に付ける、任意の文字列(接頭辞)です。
perrorは、errnoの値を見てエラーメッセージを構成するだけなので、perror自体の成否を気にする必要はありません。
出力される文字列の形式

perrorが出力する文字列は、一般的に次の形式です。
s: エラーメッセージ\n
具体例として、次のようなコードを考えます。
perror("fopen");
このとき、errnoにENOENTが入っていれば、典型的な出力は次のようになります。
fopen: No such file or directory
引数sに渡した文字列は、そのままプレフィックスとして使用されるため、どの処理でエラーが起きたのかが判別しやすくなります。
perrorの基本的な使い方
fopenとperrorを組み合わせたサンプル
まずは、存在しないファイルを開き、perrorでエラー表示を行うシンプルな例を示します。
#include <stdio.h> // perror, fopenなど
#include <errno.h> // errno
int main(void) {
// わざと存在しないファイル名を指定する
const char *filename = "no_such_file.txt";
// ファイルを読み取りモードで開く
FILE *fp = fopen(filename, "r");
// fopenが失敗するとNULLが返る
if (fp == NULL) {
// ここでerrnoにはエラー番号がセットされている
// どの操作でエラーが起きたか分かるように接頭辞を付ける
perror("fopen");
// errnoの値自体を確認したい場合はprintfなどで表示してもよい
// printf("errno = %d\n", errno);
return 1; // 異常終了
}
// ファイル操作ができていればここに処理を書く
fclose(fp);
return 0; // 正常終了
}
コンパイル・実行環境によって異なりますが、典型的には次のような出力になります。
fopen: No such file or directory
このように、perrorに処理内容を示す短い文字列を渡すことで、エラーの原因と発生箇所がひと目で分かるようになります。
errnoとperrorの関係
errnoとは何か

errnoは、標準ライブラリおよび一部の関数が失敗したときに設定する、スレッド局所(あるいはプロセス局所)なエラー番号です。
型はintで、多くの実装ではerrnoの値ごとにENOENTやEACCESなどのマクロが定義されています。
重要な点は、errnoの値は、関数が成功した場合には変化しないことがあるという点です。
そのため、エラー判定は必ず関数の戻り値で行い、その直後にerrnoを参照する必要があります。
perrorを呼び出すタイミング
perrorは常にerrnoの現在の値を見てメッセージを表示します。
そのため、エラーが発生したら、すぐにperrorを呼び出すことが重要です。
望ましいパターンは、次のような流れです。
- 関数を呼び出す。
- 戻り値で成功・失敗を判定する。
- 失敗していたら、その直後にperrorを呼び出す。
たとえばopen関数を使う場合、次のようにします。
#include <stdio.h>
#include <errno.h>
#include <fcntl.h> // open
#include <unistd.h> // close
int main(void) {
int fd = open("no_such_file.txt", O_RDONLY);
if (fd == -1) { // open失敗時は-1
// ここで既にerrnoにエラー番号が入っているので、すぐperror
perror("open");
return 1;
}
// ファイルディスクリプタが取得できていればここに処理を書く
close(fd);
return 0;
}
別の処理を挟んでしまうと、他のライブラリ関数がerrnoを書き換える可能性があるため、正しいエラー内容が分からなくなってしまいます。
perrorとstrerrorの違い
strerrorとの比較
エラーメッセージを取得する関数としてstrerrorもよく使われます。
perrorとの違いを整理しておくと理解が深まります。
以下に、簡単な比較表を示します。
| 項目 | perror | strerror |
|---|---|---|
| ヘッダ | stdio.h | string.h |
| プロトタイプ | void perror(const char *s); | char *strerror(int errnum); |
| 使うエラー番号 | 現在のerrno | 引数で渡したエラー番号 |
| 出力先 | stderrへ直接出力 | 文字列へのポインタを返す |
| 使いどころ | すぐにエラー内容を表示したいとき | メッセージを加工・保存したいとき |
perrorは「いま起きたエラーをそのまま表示する」用途に適しています。
一方で、strerrorは、エラーメッセージ文字列を手元で扱いたい、ログファイルにフォーマットして出力したい、といった場合に向いています。
strerrorと組み合わせたサンプル
perrorの代わりにstrerrorを使って、自分でメッセージを組み立てる例を示します。
#include <stdio.h>
#include <errno.h>
#include <string.h> // strerror
int main(void) {
FILE *fp = fopen("no_such_file.txt", "r");
if (fp == NULL) {
// errnoを使ってエラーメッセージを取得
const char *msg = strerror(errno);
// 自前でフォーマットして標準エラー出力へ
fprintf(stderr, "fopen error: %s\n", msg);
return 1;
}
fclose(fp);
return 0;
}
perrorを使うと1行で済む処理を、strerrorを使うと多少冗長になりますが、メッセージの形式を柔軟に制御できるという利点があります。
perror使用時の注意点とコツ
errnoを自分で書き換えない
errnoはライブラリ側が使うための変数なので、ユーザプログラムから不用意に変更しないことが基本です。
明示的に0を代入するケースもありますが、その場合は「これから行う処理でエラーが起きたかどうかを判定するために初期化しておく」といった、意図を明確にしておくことが重要です。
特に、エラー処理中に別の関数を呼び出すと、その関数がerrnoを書き換える可能性があります。
perrorを呼ぶ前に、余計な関数を挟まないよう注意してください。
出力先は標準エラー出力(stderr)
perrorはstderrに対して出力を行います。
これは通常の標準出力(stdout)とは別のストリームであり、リダイレクトやログ出力の際に役立ちます。
たとえば、Unix系環境でプログラムを実行するときに、次のようにすることで、標準出力とエラー出力を別々のファイルに保存できます。
./a.out > normal.log 2> error.log
perrorのメッセージはerror.logにのみ出力されるため、通常の結果とエラーを分けて管理することができます。
マルチスレッド環境での扱い
現代的な処理系では、errnoはスレッドごとのローカル変数として実装されています。
そのため、通常はスレッド間でerrnoの値が干渉することはありません。
ただし、スレッド内でも、1つのエラーに対して複数の関数を挟んでからperrorを呼ぶような書き方をすると、errnoが上書きされてしまう可能性があるため、やはり「エラー直後にperror」を守ることが重要です。
実践的なエラーハンドリング例
ファイルコピーとperrorの活用
最後に、perrorを活用した少しだけ実践的なサンプルとして、シンプルなファイルコピー処理を示します。
各ステップでエラーが起きた場合に、どの処理で失敗したのかが分かるようにperrorを使っています。
#include <stdio.h>
#include <errno.h>
int copy_file(const char *src, const char *dst) {
FILE *in = fopen(src, "rb");
if (in == NULL) {
perror("fopen src"); // 入力側ファイルを開く際のエラー
return -1;
}
FILE *out = fopen(dst, "wb");
if (out == NULL) {
perror("fopen dst"); // 出力側ファイルを開く際のエラー
fclose(in); // 既に開いたファイルは閉じる
return -1;
}
int ch;
while ((ch = fgetc(in)) != EOF) {
if (fputc(ch, out) == EOF) {
perror("fputc"); // 書き込みエラー
fclose(in);
fclose(out);
return -1;
}
}
if (ferror(in)) {
// 読み込み側でエラーが起きていないか確認
perror("fgetc");
fclose(in);
fclose(out);
return -1;
}
fclose(in);
fclose(out);
return 0;
}
int main(void) {
const char *src = "input.txt";
const char *dst = "output.txt";
if (copy_file(src, dst) != 0) {
fprintf(stderr, "copy_file failed.\n");
return 1;
}
printf("Copy succeeded.\n");
return 0;
}
実行時に、たとえばinput.txtが存在しなければ、次のようなエラーが出力されます。
fopen src: No such file or directory
copy_file failed.
どの処理のどの段階で失敗したのかを、perrorのプレフィックス文字列で明確にしておくことで、トラブルシューティングが格段に楽になります。
まとめ
perror関数は、errnoに格納されたエラー情報を、人間が読める形で標準エラー出力に表示するためのシンプルかつ強力な関数です。
使い方はperror("ラベル文字列");と非常に簡単ですが、エラー発生直後に呼び出すこと、errnoを不用意に書き換えないことなど、いくつかのポイントを押さえることで、信頼性の高いエラーハンドリングが行えます。
fopenやopenと組み合わせて、どの処理で何が原因で失敗したかを明確に表示しつつ、必要に応じてstrerrorでメッセージを加工するなど、場面に応じて使い分けることが大切です。
