C言語:エラー処理
C言語には例外機構が存在しない。
一般的なAPIでは、返り値と errno グローバル変数でエラーを伝える。
API 仕様書にどういう返り値と errno の値が返ってくるのかが示されている。
errno の実際の値はシステムに依存しているため、マクロで指定された名前を使う必要がある。
自分で処理を書く時にも、同様のインターフェースを使うのが望ましい。
現代では errno は、いつ誰に書き換えられるか分からないため、あまり良くないインターフェースだと考えられている。
strerror で人間が読める文章に変換することができる。
code:strerror_exp.c
int main(int argc, char *argv[])
{
int myErrno = atoi(argv1); printf("%s\n", strerror(myErrno));
return EXIT_SUCCESS;
}
code:strerror_exp.console
$ ./strerror_exp 1
Operation not permitted
$ ./strerror_exp 2
No such file or directory
perror 関数を使うと、標準エラー出力に人間が読める文章として出力される。
atoi のエラー処理の例
code:atoi_exp.c
int main(int argc, char *argv[]) {
int r;
if (argc < 2) {
fprintf(stderr, "usage: %s number\n", argv0); exit(EXIT_FAILURE);
}
errno = 0; // 関数呼び出し直前に初期化する必要あり
if (r == 0) { // エラーの時には返り値として 0 が返ってくる
if (errno != 0) { // errno == 0 の時には本当に 0。それ以外はエラー。
perror("atoi");
exit(EXIT_FAILURE);
}
}
printf("%d\n", r);
return EXIT_SUCCESS;
}
code:atoi_exp.console
$ ./atoi_exp -9999999999999999999
atoi: Numerical result out of range
glibc の実装では、内部的な変換処理でのエラーだけを拾っている模様。
新しく関数を作る場合、以下のいずれかのパターンが考えられる。
返り値に何を入れるか?
通常の返り値
通常の返り値とエラー値
成功失敗を示す値(失敗を主とする)
エラー番号
エラー番号をどう伝えるか?
返り値
Windows API の HRESULT
systemd
出力用引数
返り値を潰さず、単純スレッドセーフな方法だとこれしかない。
専用グローバル変数
C言語の errno
専用関数
Windows API の GetLastError
エラーを示す値を返り値とし(通常の返り値と当たらないようにする)、errno にエラー番号を設定する。
funcresult_t func(int arg);
C言語標準ライブラリに準拠。
POSIX もこれに準拠
エラー番号を返り値とする。
error_t func(int arg);
Windows の HRESULT
systemd
負のエラー番号を使って、正常値と分離する
成功失敗を返り値とし、引数にエラー番号の出力用ポインタを取り、そこに出力する。
result_t func(int arg, error_t *pError);
構造体にエラー値を含める。
code:struct_with_error.c
typedef struct result_or_error
{
error_t errorInfo;
result_t result;
} result_or_error_t;
result_or_error_t func();
構造体の定義コストが高い。
コンパイラでコンパイルした時、構造体は呼び出し側のスタックに用意されるかポインタとして渡されるのが一般的。(構造体のコピーコストを気にしなくてもよい)