もう迷わない!C++ std::strerrorの一般的なエラーとトラブルシューティング

2025-05-27

以下に std::strerror について詳しく説明します。

  • シグネチャ: char* strerror(int errnum);
    • errnum: エラーコードを指定する整数値。通常は、直前のシステムコールやライブラリ関数の失敗時に設定されるグローバル変数 errno の値を使用します。
    • 戻り値: エラーメッセージを表すヌル終端文字列へのポインタを返します。このポインタが指す文字列は、プログラムによって変更してはなりません。また、strerror の後続の呼び出しによって上書きされる可能性があります。
  • ヘッダー: <cstring> (C言語の <string.h> に対応)
  • 機能: 整数値で表されるエラーコード(通常は errno 変数に設定される値)を、人間が読めるエラーメッセージ文字列に変換します。

std::strerror の使い方

ファイル操作やネットワーク通信など、システムとやり取りする関数がエラーを返した場合、通常は errno というグローバル変数に特定のエラーコードが設定されます。この errno の値を std::strerror に渡すことで、どのようなエラーが発生したのかを示すメッセージを取得できます。


#include <iostream>
#include <fstream> // ファイル操作のために必要
#include <cerrno>  // errno のために必要
#include <cstring> // strerror のために必要

int main() {
    std::ifstream file("non_existent_file.txt");

    if (!file.is_open()) {
        // ファイルが開けなかった場合
        std::cerr << "ファイルのオープンに失敗しました: "
                  << std::strerror(errno) << std::endl;
        // errno は、ファイルが開けなかった理由を示すエラーコードが設定されている
    } else {
        std::cout << "ファイルは正常にオープンされました。" << std::endl;
        file.close();
    }

    // 別な例:数値を範囲外の関数に渡す場合
    // std::log(-1.0) は数学的に定義されていないため、エラーが発生する
    errno = 0; // errno をリセット
    double result = std::log(-1.0);
    if (errno == EDOM) { // EDOM は "Numerical argument out of domain" のエラーコード
        std::cerr << "log(-1) の計算に失敗しました: "
                  << std::strerror(errno) << std::endl;
    }

    return 0;
}

上記の例では、存在しないファイルを開こうとした場合や、log 関数に負の値を渡した場合に発生するエラーを std::strerror を使って表示しています。

std::strerror の注意点

  1. スレッドセーフティ: std::strerrorスレッドセーフであるとは限りません。複数のスレッドから同時に呼び出すと、予期せぬ結果になる可能性があります。最近のC++の標準では std::strerror_s (C11 の strerror_s に対応) や strerror_r (POSIX 固有) のようなスレッドセーフな代替手段が推奨されることがあります。
  2. 戻り値の有効期間: strerror が返すポインタは、静的ストレージに格納された文字列を指している場合があります。そのため、次回の strerror の呼び出しによって、以前返された文字列の内容が上書きされる可能性があります。エラーメッセージを永続的に保持したい場合は、std::string などにコピーする必要があります。
  3. ロケール依存: 返されるエラーメッセージの文字列は、現在のロケール (std::setlocale で設定される) に依存します。これにより、OSの言語設定などに応じて、異なる言語でエラーメッセージが表示されることがあります。
  4. Cスタイル文字列: std::strerrorchar* を返すため、C++の std::string と直接互換性がありません。 std::string に変換するには、std::string(std::strerror(errno)) のようにコンストラクタを利用します。
  5. errno の即時使用: errno の値は、エラーが発生した直後に参照する必要があります。なぜなら、その後の他のライブラリ関数の呼び出しによって errno の値が変更されてしまう可能性があるためです。

C++11以降では、std::system_errorstd::error_codestd::error_condition といった、より構造化されたエラー処理メカニズムが導入されています。これらは、strerror の持つスレッドセーフでない問題や、Cスタイル文字列の扱いにくさを解決するのに役立ちます。

例えば、std::make_error_code(static_cast<std::errc>(errno)).message() を使うことで、errno に対応するエラーメッセージを std::string で取得できます。これは strerror の代替としてより推奨される方法です。

#include <iostream>
#include <fstream>
#include <cerrno>
#include <system_error> // std::error_code, std::errc のために必要

int main() {
    std::ifstream file("non_existent_file.txt");

    if (!file.is_open()) {
        std::error_code ec(errno, std::system_category()); // errnoからerror_codeを作成
        std::cerr << "ファイルのオープンに失敗しました: "
                  << ec.message() << std::endl;
    } else {
        std::cout << "ファイルは正常にオープンされました。" << std::endl;
        file.close();
    }

    return 0;
}


    • エラーの状況: std::strerror は内部的に静的バッファを使用しているため、複数のスレッドから同時に呼び出すと、予期しないエラーメッセージが返されたり、データ競合が発生したりする可能性があります。あるスレッドが strerror を呼び出して文字列を取得した直後に、別のスレッドが strerror を呼び出すと、最初のスレッドが持っていたポインタが指す内容が上書きされてしまう可能性があります。
    • トラブルシューティング:
      • std::strerror_s (C11/C++11 のオプション): スレッドセーフなバージョンとして strerror_s が存在します。これは、メッセージを書き込むバッファをユーザーが指定する形式です。ただし、C++標準では必須ではありません。
      • strerror_r (POSIX): POSIXシステム(Linuxなど)では、strerror_r というスレッドセーフな関数が提供されています。これはGNUバージョンとXSI(POSIX標準)バージョンの2種類があり、シグネチャが異なるため注意が必要です。
      • std::error_code::message() の利用: C++11以降で最も推奨される方法は、std::error_code を利用することです。std::make_error_code(static_cast<std::errc>(errno)).message() のようにすることで、スレッドセーフに std::string でエラーメッセージを取得できます。これは strerror が持つ問題の多くを解決します。
  1. 戻り値の有効期間の問題(ポインタの無効化)

    • エラーの状況: std::strerrorchar* を返しますが、このポインタが指す文字列は strerror の次の呼び出しによって上書きされる可能性があります。そのため、取得したエラーメッセージをすぐに利用しない場合や、別の場所で後から参照しようとすると、期待しない内容になっていることがあります。
    • トラブルシューティング: strerror が返した文字列をすぐに std::string にコピーする。
      #include <iostream>
      #include <cerrno>
      #include <cstring>
      #include <string> // std::string を使うために必要
      
      int main() {
          // エラーを発生させる例 (存在しないファイルを開く)
          FILE* fp = fopen("non_existent_file.txt", "r");
          if (fp == nullptr) {
              std::string error_message = std::strerror(errno); // ここでコピーする
              std::cerr << "ファイルのオープンに失敗しました: " << error_message << std::endl;
      
              // ここで別の関数を呼び出しても、error_message は上書きされない
              // 例えば、何らかのログ処理など
              // std::strerror(ENOENT); // 仮に別のエラーコードでstrerrorを呼んでも...
              // std::cerr << "元のエラーメッセージは変わらない: " << error_message << std::endl;
          }
          return 0;
      }
      
  2. errno の即時使用の重要性

    • エラーの状況: errno グローバル変数の値は、エラーが発生した直後の関数呼び出しによってのみ意味を持ちます。エラーが発生した後に別の関数(特にシステムコールや標準ライブラリ関数)を呼び出すと、errno の値が上書きされてしまい、元のエラーの原因を正確に特定できなくなることがあります。
    • トラブルシューティング: エラーを検出したら、すぐに errno の値をローカル変数に保存し、その保存した値を使って strerror を呼び出す。
      #include <iostream>
      #include <fstream>
      #include <cerrno>
      #include <cstring>
      
      int main() {
          std::ifstream file("non_existent_file.txt");
      
          if (!file.is_open()) {
              int saved_errno = errno; // エラー発生直後にerrnoを保存
              // ここで別の関数を呼び出すとerrnoが変わる可能性がある
              // 例: std::cout << "何か表示" << std::endl;
              std::cerr << "ファイルのオープンに失敗しました: "
                        << std::strerror(saved_errno) << std::endl; // 保存したerrnoを使う
          }
          return 0;
      }
      
  3. 不正確な errno の値

    • エラーの状況:
      • 初期化されていない errno: errno は、エラーが発生したときに自動的に設定されるものであり、成功した場合には変更されないか、ゼロにリセットされることが保証されません。そのため、エラーが発生していないにもかかわらず、以前のエラーコードが errno に残っている場合があります。
      • 関数が errno を設定しない: すべての関数がエラー時に errno を設定するわけではありません。特にC++のストリーム操作 (std::ifstream::open など) は、failbitbadbit といったフラグを設定しますが、必ずしも errno を設定するとは限りません(ただし、ファイル操作の基盤となるシステムコールがエラーを返した場合、errno が設定されることが多い)。
    • トラブルシューティング:
      • errno = 0; でリセット: エラーチェックを行う直前に errno をゼロにリセットすることで、以前のエラーコードの影響を排除できます。ただし、その関数が実際に errno を設定するかどうかを確認する必要があります。
      • 関数の戻り値を優先: まずは関数の戻り値や例外メカニズムなど、その関数が提供する主要なエラー通知方法を確認します。errno は補助的な情報として利用します。C++のI/Oストリームの場合、is_open(), fail(), bad() といったメソッドや、exceptions() を設定して例外を捕捉する方が一般的です。
      • std::error_code の利用: std::error_code は、特定のカテゴリに属するエラーを表現するため、errno のようなシステムエラーだけでなく、より一般的なエラーも扱えます。
  4. ロケール依存性

    • エラーの状況: std::strerror が返すエラーメッセージは、現在のCロケール(具体的には LC_MESSAGES カテゴリ)に依存します。これにより、同じエラーコードでも実行環境の言語設定によって異なる言語のメッセージが表示されることがあります。テスト環境と本番環境でロケールが異なると、デバッグが難しくなる場合があります。
    • トラブルシューティング:
      • ロケールの設定: std::setlocale を使用してプログラム内で明示的にロケールを設定することで、一貫したエラーメッセージを期待できる場合があります。
      • 国際化対応の考慮: 複数の言語に対応する必要がある場合は、strerror の結果をそのままユーザーに表示するのではなく、独自のエラーメッセージテーブルを用意したり、より高度な国際化 (i18n) ライブラリを使用したりすることを検討します。
      • std::error_code::message() の利用: std::error_code::message() もロケールに依存しますが、std::strerror と同様に、プログラムが実行される環境のロケール設定に基づきます。
  5. Windows環境での非標準の挙動/警告

    • エラーの状況: WindowsのVisual C++コンパイラでは、strerror の使用に対して「安全でない関数」という警告 (C4996) が出ることがあります。これは、スレッドセーフティの問題や、バッファのオーバーフローの可能性を指摘するためです。
    • トラブルシューティング:
      • _CRT_SECURE_NO_WARNINGS の定義: 一時的な解決策として、プリプロセッサマクロ _CRT_SECURE_NO_WARNINGS を定義して警告を抑制できます。ただし、これは根本的な解決にはなりません。
      • strerror_s の使用: Windowsでは strerror_s (C11で導入された安全なバージョン) が推奨されます。
      • C++11以降の std::error_code を積極的に利用: クロスプラットフォームでより安全な方法として、std::error_code を利用するのが最善です。Windows APIのエラーコード (GetLastError()) を std::error_code に変換することも可能です。

std::strerror はレガシーなCスタイルのエラー処理によく用いられますが、現代のC++プログラミングでは、特にマルチスレッド環境での安全性を考慮すると、その利用には注意が必要です。

  • しかし、マルチスレッド環境や、エラーメッセージの永続的な保持、あるいはより堅牢なエラー処理が必要な場合は、C++11以降で導入された std::error_codestd::system_error を使用することが強く推奨されます。これらは、より型安全で、スレッドセーフなエラー処理を提供し、将来的なコードの保守性を高めます。
  • 単一スレッドで、かつ一時的なエラーメッセージ表示のみに利用する場合は、std::strerror は手軽で十分な場合もあります。


例1: 基本的なファイルオープンエラーの処理

最も一般的な std::strerror の使い方です。ファイル操作でエラーが発生した際に、その原因をユーザーに伝えます。

#include <iostream>  // 入出力ストリーム
#include <fstream>   // ファイルストリーム (std::ifstream, std::ofstream)
#include <cerrno>    // errno のために必要
#include <cstring>   // std::strerror のために必要

int main() {
    const char* filename = "non_existent_file.txt"; // 存在しないファイルを指定

    // 入力ファイルストリームを作成し、ファイルを開こうとする
    std::ifstream inputFile(filename);

    // ファイルが正常に開かれたかを確認
    if (!inputFile.is_open()) {
        // ファイルが開けなかった場合、errno の値を取得し、
        // std::strerror を使ってエラーメッセージに変換する
        std::cerr << "エラー: ファイル \"" << filename << "\" を開けませんでした。"
                  << "原因: " << std::strerror(errno) << std::endl;
        return 1; // エラー終了
    } else {
        std::cout << "ファイル \"" << filename << "\" は正常に開かれました。" << std::endl;
        // ファイル操作(読み込みなど)を行う
        // ...
        inputFile.close(); // ファイルを閉じる
    }

    return 0; // 正常終了
}

解説:

  • std::strerror(errno) は、この errno の値に対応するエラーメッセージ文字列を返します。例えば、Linuxでは "No such file or directory"、Windowsでは "The system cannot find the file specified." のようなメッセージが表示されるでしょう。
  • ファイル操作のようなシステムコールが失敗すると、通常はグローバル変数 errno にエラーコードが設定されます。
  • !inputFile.is_open() でオープンに失敗したことを検知します。
  • std::ifstream inputFile(filename); でファイルをオープンしようとします。

例2: errno の即時保存の重要性

errno の値は、エラーが発生した直後に別の関数呼び出しによって上書きされる可能性があるため、すぐに保存しておくことが重要です。

#include <iostream>
#include <fstream>
#include <cerrno>
#include <cstring>
#include <string> // std::string を使用するために必要

// 意図的にerrnoを上書きする可能性のあるダミー関数
void some_other_function() {
    // 実際にはもっと複雑な処理でerrnoが変わる可能性がある
    // 例えば、内部でファイル操作やメモリ割り当てなどが行われる場合
    // ここでは単純に errno を別の値に設定する
    // errno = 100; // 例: ダミーのエラーコードを設定
}

int main() {
    const char* filename = "another_non_existent_file.txt";

    std::ifstream inputFile(filename);

    if (!inputFile.is_open()) {
        // !!! IMPORTANT: エラーが発生したらすぐに errno の値を保存する !!!
        int saved_errno = errno;

        std::cerr << "ファイルのオープンに失敗しました: " << std::endl;

        // ここで、saved_errno を使わずに直接 errno を使った場合、
        // some_other_function() の呼び出しによって errno が変わってしまう可能性がある
        // some_other_function(); // これが呼ばれるとerrnoが変わるかもしれない

        std::cerr << "  原因 (errnoをすぐに保存した場合): "
                  << std::strerror(saved_errno) << std::endl;

        // もし保存せずに直接 errno を使っていたら...
        // std::cerr << "  原因 (errnoをすぐに保存しなかった場合 - 危険): "
        //           << std::strerror(errno) << std::endl;
        // 上の行の errno は、some_other_function() が最後に設定した値になる可能性がある

        return 1;
    } else {
        std::cout << "ファイルは正常に開かれました。" << std::endl;
        inputFile.close();
    }

    return 0;
}

解説:

  • これにより、その後にどのような関数呼び出しがあったとしても、saved_errno の値は変わらず、正確なエラーメッセージを取得できます。
  • int saved_errno = errno; で、!inputFile.is_open() が真になった直後に errno の値をローカル変数にコピーしています。

例3: 戻り値(char*)の寿命と std::string へのコピー

std::strerror が返す char* は、次回の strerror 呼び出しによって上書きされる可能性があります。そのため、エラーメッセージを後から参照したい場合は std::string にコピーする必要があります。

#include <iostream>
#include <cerrno>
#include <cstring>
#include <string> // std::string を使用するために必要

int main() {
    // エラー1: 存在しないファイルを開く場合
    // (errno = ENOENT または類似)
    errno = 0; // errnoをリセットしておく
    FILE* file1 = fopen("no_such_file_1.txt", "r");
    std::string error_msg1;
    if (file1 == nullptr) {
        error_msg1 = std::strerror(errno); // エラーメッセージをstd::stringにコピー
        std::cerr << "エラー1: " << error_msg1 << std::endl;
    }

    // エラー2: 権限がないファイルを開く場合 (または別の存在しないファイル)
    // (errno = EACCES または類似)
    errno = 0; // errnoをリセットしておく
    FILE* file2 = fopen("/root/restricted_file.txt", "r"); // 通常、root権限が必要なパス
    std::string error_msg2;
    if (file2 == nullptr) {
        error_msg2 = std::strerror(errno); // エラーメッセージをstd::stringにコピー
        std::cerr << "エラー2: " << error_msg2 << std::endl;
    }

    // ここで error_msg1 と error_msg2 は、それぞれの時点でコピーされているため、
    // 別の strerror 呼び出しによって上書きされることはない
    std::cout << "\n=== 後から参照 ===" << std::endl;
    std::cout << "最初のエラーメッセージ: " << error_msg1 << std::endl;
    std::cout << "2番目のエラーメッセージ: " << error_msg2 << std::endl;

    if (file1) fclose(file1);
    if (file2) fclose(file2);

    return 0;
}

解説:

  • これにより、その後の strerror 呼び出しによって error_msg1 の内容が変更されることを防ぎ、独立したエラーメッセージとして保持できます。
  • error_msg1 = std::strerror(errno); のように、std::string の代入演算子を利用して char*std::string に変換し、コピーしています。

例4: C++11以降のよりモダンなエラー処理 (std::error_code)

std::strerror のスレッドセーフティや戻り値の寿命といった問題を解決するため、C++11以降では std::error_code を使用することが推奨されます。

#include <iostream>
#include <fstream>
#include <cerrno>         // errno のために必要
#include <system_error>   // std::error_code, std::errc のために必要
#include <string>         // std::string のために必要

int main() {
    const char* filename = "yet_another_non_existent_file.txt";

    std::ifstream inputFile(filename);

    if (!inputFile.is_open()) {
        // errno の値とシステムカテゴリから std::error_code を作成
        // static_cast<std::errc>(errno) は、errno の値を std::errc 列挙型に安全に変換する
        // std::system_category() は、システム固有のエラーメッセージを提供するカテゴリ
        std::error_code ec(static_cast<std::errc>(errno)); // errc を使用
        // または、汎用的なエラーを扱う場合は
        // std::error_code ec = std::make_error_code(static_cast<std::errc>(errno));

        std::cerr << "エラー: ファイル \"" << filename << "\" を開けませんでした。"
                  << "原因: " << ec.message() << std::endl; // message() で文字列を取得

        // ec.message() は std::string を返すため、スレッドセーフで寿命も管理されている
        std::string error_details = ec.message();
        std::cout << "詳細エラーメッセージ (std::string): " << error_details << std::endl;

        return 1;
    } else {
        std::cout << "ファイル \"" << filename << "\" は正常に開かれました。" << std::endl;
        inputFile.close();
    }

    return 0;
}

解説:

  • ec.message() は、この error_code に対応するエラーメッセージを std::string として返します。この方法は、std::strerror の持つ多くの問題を解決します。
    • スレッドセーフです。
    • std::string を返すため、戻り値の寿命を気にする必要がありません。
    • C++のエラー処理フレームワークに統合されています。
  • std::error_code ec(static_cast<std::errc>(errno)); または std::make_error_code(static_cast<std::errc>(errno)); を使用して、errno の値から std::error_code オブジェクトを作成します。std::errc は標準で定義されたシステムエラーコードに対応する列挙型です。

std::perror は、std::strerror(errno) と同じメッセージを標準エラー出力に直接出力する関数です。簡単なデバッグには便利ですが、カスタマイズ性は低いです。

#include <iostream>
#include <cstdio>  // std::perror のために必要
#include <fstream>
#include <cerrno>

int main() {
    const char* filename = "yet_another_file.txt";

    std::ifstream inputFile(filename);

    if (!inputFile.is_open()) {
        std::cerr << "エラー: ファイル \"" << filename << "\" を開けませんでした。" << std::endl;
        // std::perror は、引数に与えられた文字列の後にコロン、スペース、
        // そして strerror(errno) の結果を出力する
        perror("ファイルオープン時のシステムエラー"); // "ファイルオープン時のシステムエラー: No such file or directory" のように出力される
        return 1;
    } else {
        std::cout << "ファイル \"" << filename << "\" は正常に開かれました。" << std::endl;
        inputFile.close();
    }

    return 0;
}

解説:

  • 簡潔なデバッグ出力には便利ですが、出力形式を細かく制御したい場合には std::strerrorstd::error_code::message() を自分で結合する方が柔軟です。
  • perror("ファイルオープン時のシステムエラー"); の呼び出しは、内部的に fprintf(stderr, "%s: %s\n", "ファイルオープン時のシステムエラー", strerror(errno)); と似たような動作をします。


これらの課題を解決し、よりC++らしいエラー処理を実現するための代替手段は以下の通りです。

std::error_code と std::system_category (C++11以降)

C++11で導入されたエラー処理の標準メカニズムであり、最も推奨される代替手段です。システムエラー(errno が示すようなエラー)と汎用的なエラーの両方を、型安全かつスレッドセーフに扱えます。

  • std::system_error との連携: エラーを検知した時点で、そのエラーを例外として上位に通知したい場合に利用します。

    #include <iostream>
    #include <fstream>
    #include <cerrno>
    #include <system_error> // std::system_error のために必要
    
    void open_file_safe(const std::string& path) {
        std::ifstream file(path);
        if (!file.is_open()) {
            // エラーコードとメッセージを含む例外をスロー
            // std::error_code は暗黙的に std::system_error のコンストラクタに渡せる
            throw std::system_error(errno, std::system_category(), "ファイルオープンに失敗しました");
        }
        std::cout << "ファイル \"" << path << "\" は正常に開かれました。" << std::endl;
        file.close();
    }
    
    int main() {
        try {
            open_file_safe("non_existent_file.txt");
        } catch (const std::system_error& e) {
            std::cerr << "例外を捕捉しました: " << e.what() << '\n';
            std::cerr << "  エラーコード: " << e.code().value() << '\n';
            std::cerr << "  カテゴリ: " << e.code().category().name() << '\n';
        }
        return 0;
    }
    
  • 使い方:

    #include <iostream>
    #include <fstream>
    #include <cerrno>       // errno のために必要
    #include <system_error> // std::error_code, std::system_category のために必要
    #include <string>       // std::string のために必要
    
    int main() {
        const char* filename = "non_existent_file.txt";
        std::ifstream file(filename);
    
        if (!file.is_open()) {
            // errno の値から std::error_code を作成
            // std::errc は errno の標準的な値に対応する列挙型(C++11)
            // std::system_category() はシステムエラーのカテゴリを返す
            std::error_code ec(errno, std::system_category());
    
            std::cerr << "エラー: ファイル \"" << filename << "\" を開けませんでした。\n"
                      << "  エラーコード (数値): " << ec.value() << "\n"
                      << "  カテゴリ名: " << ec.category().name() << "\n"
                      << "  メッセージ: " << ec.message() << std::endl; // std::string を返す
    
            // あるいは、std::make_error_code を使用することもできる(より簡潔)
            // std::error_code ec_make = std::make_error_code(static_cast<std::errc>(errno));
            // std::cerr << "  メッセージ (make_error_code): " << ec_make.message() << std::endl;
            return 1;
        }
        std::cout << "ファイル \"" << filename << "\" は正常に開かれました。" << std::endl;
        file.close();
        return 0;
    }
    
  • 特徴:

    • 型安全: エラーコードとエラーカテゴリ(そのエラーがどの「種類」に属するかを示す)を組み合わせて表現します。
    • スレッドセーフ: message() メンバー関数は std::string を返すため、strerror のような内部バッファの共有による問題がありません。
    • 寿命の管理: std::string を返すため、戻り値の寿命を気にする必要がありません。
    • 拡張性: 独自のカスタムエラーカテゴリを作成し、アプリケーション固有のエラーを統一的に扱えます。
    • 例外との連携: std::system_error 例外クラスと組み合わせて、エラーを例外としてスローできます。

POSIX の strerror_r (非標準)

POSIXシステム(Linux, macOS など)で利用可能なスレッドセーフな strerror の代替です。Windowsでは利用できません。

  • 注意点:

    • クロスプラットフォーム対応が必要な場合は、std::error_code の方がはるかに良い選択肢です。
    • XSIバージョンとGNUバージョンの違いは、移植性を考える上で非常に重要です。
  • 使い方 (XSI/POSIX バージョン):

    #include <iostream>
    #include <cerrno>
    #include <cstring>   // strerror_r のために必要
    #include <string>
    #include <vector>    // char バッファの管理のために
    
    // POSIX準拠の strerror_r を使うために_XOPEN_SOURCEを定義することがある
    // #define _XOPEN_SOURCE 600
    
    int main() {
        // エラーを発生させる (例: 存在しないファイル)
        errno = 0; // errnoをリセット
        FILE* fp = fopen("non_existent_file.txt", "r");
    
        if (fp == nullptr) {
            const int BUF_SIZE = 256;
            std::vector<char> err_buf(BUF_SIZE); // バッファを準備
    
            // strerror_r (POSIX/XSIバージョン) を呼び出し
            // 返り値は成功なら0、エラーならエラー番号
            int result = strerror_r(errno, err_buf.data(), err_buf.size());
    
            if (result == 0) {
                std::cout << "エラー: ファイルオープン失敗\n"
                          << "  メッセージ: " << std::string(err_buf.data()) << std::endl;
            } else {
                std::cerr << "エラー: strerror_r 自体が失敗しました (エラーコード: "
                          << result << ")" << std::endl;
            }
        } else {
            std::cout << "ファイルは正常に開かれました。" << std::endl;
            fclose(fp);
        }
        return 0;
    }
    
  • 特徴:

    • スレッドセーフ: ユーザーが提供するバッファにエラーメッセージを書き込みます。
    • 2つのバージョン: POSIX標準 (_XOPEN_SOURCE >= 600) と GNU拡張 (_GNU_SOURCE) の2つの異なるシグネチャが存在し、互換性に注意が必要です。
      • XSI (POSIX) バージョン: int strerror_r(int errnum, char *buf, size_t buflen);
        • 成功時に 0 を返し、エラー時にエラー番号を返します。
        • buf にメッセージが書き込まれます。
      • GNU バージョン: char *strerror_r(int errnum, char *buf, size_t buflen);
        • buf または内部静的バッファへのポインタを返します。
        • 成功時に buf へのポインタを返し、失敗時に NULL を返します(またはエラーメッセージへのポインタ)。

C11で導入された、バッファオーバーフローを防ぐことを目的とした「安全な」strerror です。C++11/14から <cstring> ヘッダーに含まれることがありますが、実装はコンパイラ依存で、std::error_code ほど広く推奨されていません。

  • 注意点:

    • 全てのコンパイラや標準ライブラリで strerror_s が利用できるとは限りません。移植性を考慮すると std::error_code がより信頼できます。
  • 使い方 (Visual C++ 環境などで有効):

    #include <iostream>
    #include <cerrno>
    #include <cstring>   // strerror_s のために必要
    #include <string>
    #include <vector>
    
    int main() {
        errno = 0;
        FILE* fp = fopen("non_existent_file.txt", "r");
    
        if (fp == nullptr) {
            const int BUF_SIZE = 256;
            std::vector<char> err_buf(BUF_SIZE);
    
            // strerror_s を呼び出し (バッファとバッファサイズを渡す)
            // Windows環境のVisual C++で特に利用される
            // 成功なら0を返す
            int result = strerror_s(err_buf.data(), err_buf.size(), errno);
    
            if (result == 0) {
                std::cout << "エラー: ファイルオープン失敗\n"
                          << "  メッセージ: " << std::string(err_buf.data()) << std::endl;
            } else {
                std::cerr << "エラー: strerror_s 自体が失敗しました (戻り値: "
                          << result << ")" << std::endl;
            }
        } else {
            std::cout << "ファイルは正常に開かれました。" << std::endl;
            fclose(fp);
        }
        return 0;
    }
    
  • 特徴:

    • バッファを引数で渡す: strerror_r と同様に、メッセージを書き込むバッファとサイズを呼び出し側が指定します。
    • スレッドセーフ: バッファが呼び出し側から提供されるため、スレッドセーフです。
    • Windowsでの利用: Microsoft Visual C++ では、strerror の安全な代替として strerror_s が推奨されます。
    • 戻り値: int を返し、成功時に 0 を返します。
代替手段特徴考慮事項推奨度
std::error_codeC++標準、型安全、スレッドセーフ、寿命管理不要、拡張性高いC++11以降の機能。最高 (最も推奨)
strerror_r (POSIX)スレッドセーフ、バッファをユーザー指定。POSIX固有(非標準)、XSIとGNUの2バージョンに注意。限定的 (POSIXのみ)
strerror_s (C11/Windows)スレッドセーフ、バッファをユーザー指定、バッファオーバーフロー対策。C11由来(非標準)、コンパイラ依存、主にWindowsで利用。限定的 (Windows中心)