【C言語】
printf(書式%s)にNULLポインタが渡る時 “(null)”の表示を期待しないこと

warning: ‘%s’ directive argument is null

[-Wformat-overflow=]


■この記事の概要

この記事では、C言語のprintfで書式指定子%sNULLポインタを渡すと未定義動作となる問題を解説しています。(null)と表示されるケースもありますが、セグメンテーションフォルトが発生することもあるため、NULLを渡さないようにする対策が推奨されています。例えば、戻り値チェックを徹底することで安全性を確保できます。


■書式%sにNULLポインタが渡るとSegmentation fault

//puts()に置き換えれる時だけ落ちる
//  printf("%s\n",NULL)->puts(NULL)
#include <stdio.h>

int main(void){
    //以下未定義動作
    printf("前[%s]\n",  NULL); fflush(NULL);
    printf("[%s]後\n",  NULL); fflush(NULL);
    printf("[%s]",      NULL); fflush(NULL);
    printf("%s\t",      NULL); fflush(NULL);

    //落ちる
    printf("%s\n",      NULL); fflush(NULL);
}
./a.out
前[(null)]
[(null)]後
[(null)](null)  Segmentation fault

Windows系のコンパイラでは書式%sにNULLポインタが渡ると
“(null)”と表示されるそうですが、
Linux系(gcc,clang)では
コンパイラと最適化レベルで結果が異なりました。
”(null)”と表示されたり
Segmentation faultと表示して異常終了したりします。

推測ですがgccの場合printf自体には%sにNULLが来ても(null)と表示する機能があるのですが
printf(“%s\n”,NULL)➡puts(NULL)の最適化が行われると異常終了するようです。

いずれにせよ未定義動作なので
“%s”にNULLが渡らないようにするのが賢明です。


■戻り値をチェックしないでNULLポインタとなる例

#include    <string.h>
#include    <stdio.h>
int main(void){
    const char *cp = "abc,def,ghi";    
    printf("%s\n",cp);

    cp = strchr(cp,',');
    printf("%s\n",cp);
    fflush(stdout);

    cp = strchr(cp+1,',');
    printf("%s\n",cp);
    fflush(stdout);

    cp = strchr(cp+1,',');
    printf("%s\n",cp);
    fflush(stdout);
}
./a.out
abc,def,ghi
,def,ghi
,ghi
Segmentation fault (コアダンプ)


書式%sにNULLポインタを渡すのはC言語規格外の未定義動作なので
職業プログラマはきちんとstrchr()の返却値をチェックして
書式%sにNULLポインタが渡らないようにチェックしましょう。

参考:

FIO47-C. 書式指定文字列を正しく使う

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