【C言語】エラーを返すscanf関数の戻り値をチェックしよう(あれば便利程度strcpyの戻り値は無視してOK)

warning: ignoring return value of ‘scanf’ declared with attribute ‘warn_unused_result’

警告:属性’warn_unused_result’で宣言された’scanf’の戻り値を無視しています。
[-Wunused-result]

■この記事の概要

この記事では、C言語でscanf関数の戻り値を確認する重要性を解説しています。書式エラー時の不具合を防ぐため、fgetssscanfの併用を推奨。また、strcpyの戻り値は無視しても問題ないケースについても説明しています。


■”戻り値が無視されました”の意味

scanf 等の関数は処理に失敗すると
エラーを戻り値で返します。

でもプログラマが戻り値を確認して
エラーメッセージ等を画面に表示しないと、
ユーザーはエラーに気が付きません。

なので親切?なコンパイラは
”戻り値のエラー確認しなくて良いの?”と
警告するわけです。


■警告の出る例

#include    <stdio.h>
//scanf(警告あり)
int main(void) {
    for(;;){
        int     age ;
        char    name[256];
        printf("Usage:name age\n");
        scanf("%s %d",name,&age);
        printf("name=%s age=%d\n",name,age);
    }
}

一般的にエラーを返す関数の戻り値をチェックするのが
プロの仕事ですが、
実はscanf関数は特別で戻り値をチェックしても
プログラムはうまく動きません。
scanf関数を使うのは止めて fgets()+sscanf()を使いましょう。

scanf()は
書式に一致し引数に代入が成功した入力要素の個数を返します。
上記コードの場合、
書式変換に成功するとscanf()は2を返却します。(%s と%d の2変換)
書式変換に失敗するとscanf()は2未満の変換に成功した数、またはEOFを返します。

書式変換に失敗した場合、出力引数(name,age) には適切な値は設定されていません。

■推奨しない修正案

#include <stdio.h>
//scanf 戻り値チェック版(警告無し)
int main(void) {
    for(;;){
        int     age ;
        char    name[256];
        printf("Usage:name age\n");
        if(scanf("%s %d",name,&age) != 2) {
            printf("入力形式が違います");
            continue;
        }
        printf("name=%s age=%d\n",name,age);
    }
}

scanfの戻り値をチェックすると、コンパイラの警告は消えます。
しかし入力が正しくない場合、このコードでも暴走してしまい正しくエラー処理ができません。

■推奨する修正案

#include <stdio.h>
//fgets+sscnaf版(推奨)
int main(void){
    for(;;){
        int     age ;
        char    name[256];
        char    buf[BUFSIZ];
        printf("Usage:name age\n");
        if(fgets(buf,BUFSIZ,stdin) == NULL){
            break;
        }
        if(sscanf(buf,"%s %d",name,&age) != 2) {
            printf("入力形式が違います");
            continue;
        }
        printf("name=%s age=%d\n",name,age);
    }
}

scanfは入力形式に異常があるとうまくエラー処理ができないので
変わりに fgets()+sscanf()を使用する事を推奨します。

■戻り値を無視して良い場合

#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>
int main(void)
{
    //あれば便利程度の戻り値は無視して良い
    char    dst[1024];
    strcpy(dst,"戻り値は無視して良い");
    printf("%s\n",dst);
    (void)strcpy(dst,"このキャストは不要です");
    printf("%s\n",dst);
}

strcpy()のようにあれば便利程度の戻り値は無視してもかまいません。
熱血に(void)strcpy()とキャストする必要もありません。

静的解析ツールがstrcpy()の戻り値を無視したと冗長警告を出す場合、警告除外リスト(=ホワイトリスト)に登録して、戻り値を無視しても問題ないことを静的解析ツールに教え込めないか検討してみてください。

参考:

EXP12-EX2: 関数呼び出しが必ず成功するあるいは返り値がエラー条件を示さないのであれば、返り値を無視できる。

https://www.jpcert.or.jp/sc-rules/c-exp12-c.html

C-FAQ 12.19: [前略] scanf()の戻り値を調べるほうが安全だと考え た。けれど、たまに無限ループに入ってしまうようだ。

http://www.kouno.jp/home/c_faq/c12.html#19

C-FAQ 12.20:どうして誰もがscanf()を使わないほうがいいというのか。

http://www.kouno.jp/home/c_faq/c12.html#20

YAHOO知恵袋:戻り値が無視されました