閉じる

C言語のperror関数とは?基本構文と使い方を解説

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の宣言は、標準的な処理系では次のようになっています。

C言語
void perror(const char *s);

ポイントは次の通りです。

  • 戻り値はvoid … 成功・失敗という概念はなく、単にメッセージを出力するだけです。
  • 引数はconst char *s … メッセージの先頭に付ける、任意の文字列(接頭辞)です。

perrorは、errnoの値を見てエラーメッセージを構成するだけなので、perror自体の成否を気にする必要はありません。

出力される文字列の形式

perrorが出力する文字列は、一般的に次の形式です。

s: エラーメッセージ\n

具体例として、次のようなコードを考えます。

C言語
perror("fopen");

このとき、errnoにENOENTが入っていれば、典型的な出力は次のようになります。

実行結果
fopen: No such file or directory

引数sに渡した文字列は、そのままプレフィックスとして使用されるため、どの処理でエラーが起きたのかが判別しやすくなります。

perrorの基本的な使い方

fopenとperrorを組み合わせたサンプル

まずは、存在しないファイルを開き、perrorでエラー表示を行うシンプルな例を示します。

C言語
#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の値ごとにENOENTEACCESなどのマクロが定義されています。

重要な点は、errnoの値は、関数が成功した場合には変化しないことがあるという点です。

そのため、エラー判定は必ず関数の戻り値で行い、その直後にerrnoを参照する必要があります。

perrorを呼び出すタイミング

perrorは常にerrnoの現在の値を見てメッセージを表示します。

そのため、エラーが発生したら、すぐにperrorを呼び出すことが重要です。

望ましいパターンは、次のような流れです。

  1. 関数を呼び出す。
  2. 戻り値で成功・失敗を判定する。
  3. 失敗していたら、その直後にperrorを呼び出す。

たとえばopen関数を使う場合、次のようにします。

C言語
#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との違いを整理しておくと理解が深まります。

以下に、簡単な比較表を示します。

項目perrorstrerror
ヘッダstdio.hstring.h
プロトタイプvoid perror(const char *s);char *strerror(int errnum);
使うエラー番号現在のerrno引数で渡したエラー番号
出力先stderrへ直接出力文字列へのポインタを返す
使いどころすぐにエラー内容を表示したいときメッセージを加工・保存したいとき

perrorは「いま起きたエラーをそのまま表示する」用途に適しています。

一方で、strerrorは、エラーメッセージ文字列を手元で扱いたい、ログファイルにフォーマットして出力したい、といった場合に向いています。

strerrorと組み合わせたサンプル

perrorの代わりにstrerrorを使って、自分でメッセージを組み立てる例を示します。

C言語
#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を使っています。

C言語
#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でメッセージを加工するなど、場面に応じて使い分けることが大切です。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!