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] |
|
翻訳元
これは以下のページを翻訳したものです。



