ERR30-C. 関数を呼び出す前に errno をゼロに初期化し、関数の異常終了時にのみ errno を参照する
プログラム開始時のerrno
の値は0であるが、この値はライブラリ関数によって0に設定されることはない。C言語仕様において関数が errno
を使用することが規定されていない場合、エラーの有無に関係なく、そのライブラリ関数の呼出しにより errno
がゼロ以外の値に設定されるかもしれない。errno
の値を検査することが意味を持つのは、エラーが報告された後だけである。より正確にいうと、errno
は、エラー時に errno
を設定するライブラリ関数がエラーコードを返した後にのみ意味を持つ。
C-FAQ の Question 20.4 [Summit 2005] には次のように書かれている。
一般的に、エラーの検出は関数の返り値をチェックすることで行うべきであり、
errno
を使用するのはエラーの発生原因 (たとえば "File not found" や "Permission denied") を特定するためだけにすべきである(perror
やstrerror
を使用して区別したエラーメッセージを出力することはよくある)。関数が、一意の、正規の返り値以外の値でエラー時の返り値を表せない場合にのみ、errno
を使用してエラーを検出する必要がある (atoi
のようにすべての返り値が有効な値である場合)。その場合には(かつこの場合に限る。関数がこれに当てはまるかどうかはC言語仕様を確認のこと)、errno
を0にセットしてから関数を呼び出し、そのあとでerrno
を検査することで、エラーを検出することができる(はじめにerrno
を0に設定しておくことが重要である。ライブラリ関数はこれを行わない)。
ちなみに atoi()
は errno
の値を設定すると規定されてはいない.
ライブラリ関数は、次に示すカテゴリのいずれかに分類される。
errno
を設定し out-of-band error indicator (正規の返り値の範囲にないエラー値)を返すerrno
を設定し in-band error indicator (正規の返り値の範囲にあるエラー値)を返すerrno
を設定するかどうか規定されていない- 他の規格において言及されている
errno
を設定し正規の返り値の範囲にないエラー値を返すライブラリ関数
以下のテーブルに示す関数は、errno
に値を設定し out-of-band error indicator を返す関数として C 言語規格で規定されているものである。つまり、エラー発生時にこれらの関数が返す値は、正常終了時の返り値とは異なる範囲の値である。
これらの関数を呼び出すプログラムでは、発生したエラーを調べるために errno
の値をチェックしてもよい。ただし、errno
の値をチェックする前に関数の返り値がエラーを示していることを確認すること。例えば、signal()
呼出しでは、その返り値が SIG_ERR
であることを確認してから errno
の値をチェックする。
errno
に値を設定し Out-of-Band Error Indicator を返す関数
関数名 |
返り値 |
|
---|---|---|
ftell() |
-1L |
正の値 |
|
Nonzero |
|
|
|
|
|
|
正の値 |
|
|
|
mbrtoc16() , mbrtoc32() |
(size_t)(-1) |
EILSEQ |
c16rtomb() , c32rtomb() |
(size_t)(-1) |
EILSEQ |
errno
を設定し正常終了時の返り値の範囲にあるエラー値を返すライブラリ関数
以下のテーブルに示す関数は、errno
に値を設定し in-band error indicator を返す関数として C 言語規格で規定されているものである。つまり、これらの関数がエラー発生時に返す値は、正常終了時の返り値の範囲にある値である。例えば strtoul()
関数は、エラーが発生した場合 ULONG_MAX
を返し、errno
に ERANGE
を設定する。ULONG_MAX
は正常終了時の返り値としても使われる値であるため、エラーが発生したかどうかは errno
の値から判別しなければならない。エラーの識別を errno
で行う場合には、関数呼出しを行う前に errno
の値を 0 に設定すること。また、他の関数呼出しを行う前に errno
の値を確認しなければならない。
fgetwc()
関数と fputwc()
関数が WEOF
を返す状況は複数あり、そのなかの1つだけが errno
の値を設定する。また、文字列を変換する関数は、変換した結果が返り値の型で表現できない場合、返り値の型で表現できる最大値あるいは最小値を返すとともに、errno
に ERANGE
を設定する。しかし、入力が不正なものあったためにそもそも変換が行われなかった場合には、関数の返り値は 0、出力用のポインタ引数の値が null ポインタでない場合にはそのポインタが指す場所に入力用のポインタ引数の値が書き込まれる。
errno
に値を設定し In-Band Error Indicator を返す関数
関数名 |
返り値 |
|
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
0 あるいは ±HUGE_VALL |
|
|
|
|
errno
の値を設定するかどうか規定されていないライブラリ関数
C 言語規格では、一部の関数について errno
をどう扱うかを規定していない。例えば、setlocale()
関数はエラー発生時に null ポインタを返すが、errno
を設定する保証はない。
これらの関数を呼び出した後、errno
の値のみに依存してエラーの発生を判断してはならない。関数は errno
の値を変更したかもしれないが、errno
がエラーの状態を適切に示しているという保証はない。
他の規格で動作が規定されているライブラリ関数
一部の関数は、他の規格で errno
の扱いに関して異なる挙動が規定されている。一例として fopen()
がある。fopen()
はエラーに遭遇すると null ポインタを返すが、このときの errno
の扱いについて C 言語規格では何も言及していない。しかし POSIX.1 では、fopen()
がエラーに遭遇したときには null ポインタを返すとともに errno
にエラー内容を表す値を設定する、と規定している[IEEE Std 1003.1-2013]。したがって、C 言語規格に準拠し POSIX に準拠しないプログラム(例えば Windows で動作するプログラム)では、fopen()
を呼出した後に errno
の値からエラー内容をチェックするような動作をするべきではない。一方、POSIX 準拠のプログラムでは fopen()
が null ポインタを返したときには errno
の値を調べるのが正しい挙動ということになる。
ライブラリ関数と errno
以下に示す errno
の使用法はC言語規格に規定されている。
<complex.h>
に定義されている関数はerrno
を設定してもよいが、必ずしも設定する必要はない。strtod()
、strtol()
、wcstod()
、およびwcstol()
系列の数値変換関数では、正しい計算結果が表現可能な範囲を超えているとき、しかるべき最小値または最大値を返し、errno
に値ERANGE
を格納する。strtod()
およびwcstod()
系列の浮動小数点数変換関数では、アンダーフローが発生した場合にerrno
にERANGE
を設定するかどうかは処理系定義である。変換処理が失敗した場合には0
を返し、errno
にはなにも設定しない。- 数値変換関数
atof()
およびatoi()
系列の数値変換関数は、errno
の「値に必ずしも影響を及ぼさない」。 <math.h>
に定義されている数学関数について、整数式math_errhandling & MATH_ERRNO
がゼロ以外の場合、定義域エラー発生時にerrno
の値はEDOM
となる。既定の丸め動作によるオーバーフロー発生時、あるいは結果が数学的に無限大であれば、errno
の値はERANGE
となる。アンダーフローが発生した場合、errno
の値がERANGE
となるかどうかは処理系定義である。signal()
の呼び出しによって行われた要求を満たせない場合、SIG_ERR
の値が返り、errno
には正の値が格納される。- バイト入出力関数、ワイド文字入出力関数、およびマルチバイト文字変換関数は、エンコーディングエラーが発生した場合かつその場合に限り、
errno
にマクロEILSEQ
の値を格納する。 fgetpos()
およびfsetpos()
は失敗した場合ゼロ以外を返し、処理系定義の正の値をerrno
に格納する。ftell()
は失敗した場合-1L
を返し、処理系定義の正の値をerrno
に格納する。perror()
関数はerrno
に格納されたエラー番号とメッセージを対応付け、メッセージをstderr
に書き出す。
POSIX.1標準は、より多くの関数(C 標準ライブラリ関数も含む)について errno
の使用を定義している。このルールの例外となる関数もいくつか存在する。これらの関数は、エラーを示すために予約された返り値を持たず、エラー時に errno
を設定する。エラーを検出したければ、関数の呼出し前に errno
を0に設定し、呼出し後にそれが非ゼロの値かどうかをチェックする。対象となる関数は、strcoll()
、strxfrm()
、strerror()
、wcscoll()
、wcsxfrm()
、fwide()
である。注意しなくてはならないのは、C言語規格では、これらの関数の成功時に errno
を非ゼロの値に設定してもよいとしていることである。したがって、この種のエラーチェックはPOSIXシステムでのみ行うべきである。
違反コード(strtoul()
)
以下の違反コードでは、strtoul()
を呼び出す前に errno
を0に設定していない。strtoul()
は、エラーが発生した場合に ULONG_MAX
(正常な処理結果としても有効な値)を返すため、errno
は strtoul()
が成功したことを判断する唯一の手段である。
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
void func(const char *c_str) {
unsigned long number;
char *endptr;
number = strtoul(c_str, &endptr, 0);
if (endptr == c_str || (number == ULONG_MAX
&& errno == ERANGE)) {
/* エラー処理 */
} else {
/* 計算成功 */
}
この方法で検出されたエラーは、このコードの前で発生していたかもしれないし、実際に発生したエラーを表していない可能性もある。
適合コード (strtoul()
)
以下の適合コードでは、strtoul()
を呼び出す前に errno
を 0
に設定し、呼び出した後に errno
を検査している。
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
void func(const char *c_str) {
unsigned long number;
char *endptr;
errno = 0;
number = strtoul(c_str, &endptr, 0);
if (endptr == c_str || (number == ULONG_MAX
&& errno == ERANGE)) {
/* エラー処理 */
} else {
/* 計算成功 */
}
}
違反コード (fopen()
)
fopen()
はエラーが発生しても errno
を設定するとはかぎらないので、次に示すコードはエラーを検出できない可能性がある。
#include <errno.h>
#include <stdio.h>
void func(const char *filename) {
FILE *fileptr;
errno = 0;
fileptr = fopen(filename, "rb");
if (errno != 0) {
/* エラー処理 */
}
}
適合コード (fopen()
, C)
C 言語規格の fopen()
の説明では、errno
に言及していない。この適合コードでは、errno
ではなく fopen()
の返り値からエラーかどうかを判別している。
#include <stdio.h>
void func(const char *filename) {
FILE *fileptr = fopen(filename, "rb");
if (fileptr == NULL) {
/* fopen() でエラーが発生した場合の処理 */
}
}
適合コード (fopen()
, POSIX)
以下の適合コードでは、他の手段でエラーが検出された場合にのみ errno
をチェックしている。
#include <errno.h>
#include <stdio.h>
void func(const char *filename) {
FILE *fileptr;
errno = 0;
fileptr = fopen(filename, "rb");
if (fileptr == NULL) {
/*
* fopen() でエラーが発生していることが確認できた
* errno をチェックすることは有効
*/
perror(filename);
}
}
リスク評価
errno
を誤用すると、エラーを検出できなかったり、エラーではないのに誤ってエラーと判断してしまう恐れがある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
ERR30-C |
中 |
中 |
中 |
P8 |
L2 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Astrée |
22.04
|
errno-reset | Partially checked |
Axivion Bauhaus Suite |
7.2.0 |
CertC-ERR30 | Fully implemented |
CodeSonar |
7.0p0
|
LANG.STRUCT.RC |
Redundant Condition |
Compass/ROSE |
|
|
各ライブラリ関数を使用する際の |
Coverity |
2017.07
|
MISRA C 2012 Rule 22.8 MISRA C 2012 Rule 22.9 MISRA C 2012 Rule 22.10 |
実装済み |
Helix QAC |
2022.1 |
C2500, C2501, C2502, C2503 C++3172, C++3173, C++3174, C++3175, C++3176, C++3177, C++3178, C++3179, C++3183, C++3184 |
|
Klocwork |
2022.1
|
CXX.ERRNO.NOT_SET |
|
LDRA tool suite |
9.7.1
|
111 D, 121 D, 122 D, 132 D, 134 D |
実装済み |
Parasoft C/C++test |
2021.2
|
CERT_C-ERR30-a |
Properly use errno value |
R2022a |
|
Checks for:
Rule fully covered. |
|
PRQA QA-C |
9.7 |
2500, 2501, 2502, 2503 |
|
関連するガイドライン
Taxonomy |
Taxonomy item |
Relationship |
---|---|---|
CERT C コーディングスタンダード | EXP12-C. 関数の返り値を無視しない | Prior to 2018-01-12: CERT: Unspecified Relationship |
ISO/IEC TS 17961:2013 | Incorrectly setting and using errno [inverrno] |
Prior to 2018-01-12: CERT: Unspecified Relationship |
CWE 2.11 | CWE-456, Missing Initialization of a Variable | 2017-07-05: CERT: Rule subset of CWE |
参考資料
[Brainbell.com] | Macros and Miscellaneous Pitfalls |
[Horton 1990] | Section 11, p. 168 Section 14, p. 254 |
[IEEE Std 1003.1-2013] | XSH, System Interfaces, fopen |
[Koenig 1989] | Section 5.4, p. 73 |
[Summit 2005] |
|
翻訳元
これは以下のページを翻訳したものです。