【C言語】
NULL Pointer Dereference
間接参照/逆参照/参照外し

NULL Pointer Dereference 間接参照/逆参照/参照外し

warning: dereference of NULL

警告:NULL ポインタ参照
警告:NULL ポインタの逆参照
警告:NULL ポインタの間接参照

※上記3警告は日本では皆同じ意味で、
「NULLポインタの指す先を参照する」の意味です。

[-Wanalyzer-null-dereference]

■初歩的な間違い

#include <stdio.h>
int main(void){
    int *ptr = NULL;
    *ptr = 0;   //★NG:NULLポインタ参照が発生する
}

ptr を NULLで初期化していますが、
これはまだメモリの何処も指していない状態です。
これをNULLポインタと言います。
例えるならば葉書の住所欄がマッサラの状態です。

NULLポインタの指す先を参照すると
多くの場合プログラムは異常終了します。

●修正例

int main(void){
    int x;
    int *ptr = &x;
    *ptr = 0;   //OK:ポインタは変数xを参照してる
}

ptr は変数 x のアドレスを指しています。
例えるならば葉書の住所欄がしっかり記入されている状態です。


■関数跨ぎのNULLポインタ参照

#include <stdio.h>
void f(int *p) {
    *p = 1 ;//★NG:関数跨ぎのNULL参照
}
int main(void) {
    f(NULL) ;            
}

NULLポインタは引数で渡ってくる場合が多いので、
関数を跨いだり、ファイルを跨いだりした場合に
問題検出は指数的に難しくなってきます。

残念ながら、この問題は
gcc/clang -Wall -Wextra では検出できません。
※最近追加された gcc -fanalyzer オプションを使うと検出できます。

■NULLチェックを後で行うのは誤り

#include    <stdio.h>
enum    {OK,NG};
int f(int   *p) {
    *p = 0 ;        //とりあえず初期化
    
    if(p==NULL){    //NULLチェック
        return  NG;
    }
    
    *p = 81084126;  //本処理 
    
    return  OK;
}
int main(void) {
    f(NULL);
}

とりあえず初期化してしまった後で、
NULLチェックしても意味がありません。
※交通事故を起こした後で左右確認しても意味がないのと同じです。

■NULLチェックを右で行うのは誤り

#include    <stdio.h>
typedef struct  {  int x; } tag ;
int 悪い例(tag *p)  {
    if(p->x < 0 && p!=NULL)//NULLチェックが後
        return  -1;
    return  0;
}
int 良い例(tag *p)  {
    if(p!=NULL && p->x < 0)//NULLチェックが先
        return  -1;
    return  0;
}
int main(void){
    printf("%d\n",良い例(NULL));
    printf("%d\n",悪い例(NULL));
}

●NG:if(p->x < 0 && p!=NULL)

p->x を参照した後で p のNULLチェックをしても手遅れです。

●OK:if(p !=NULL&& p->x <0)

p のNULLチェックをした後でp->xを参照ているのでOKです。


■戻り値をチェックしないのは手抜き

#include    <malloc.h>
#include    <string.h>
int main(void) {
    //戻り値をチェックせずにいきなり書き込み
    int *data = malloc(1024);
    data[0] = '\0' ;//NG
    free(data);
    
    //戻り値をチェックせずにいきなり関数引数
    char *dst = malloc(1024);
    strcpy(dst,"src");//NG
    printf("%s\n",dst);
    free(dst);
}

malloc()がNULLを返すかもしれないので戻り値を確認してください。


■書式%sにNULLを渡してはいけない

void f(char *csv){
    char *p = strchr(csv,',');
    //書式%sにNULLが渡るかもしれない
    printf("%s\n",p);
}
int main(void){
    f("abc,def,ghi");
    f("ghi");
}

strchr()がNULLを返すかもしれないので戻り値を確認してください。
書式%sにNULLポインタを渡すのはC言語規格外の未定義動作なので
Windwos系はうまく動くようですが、
gcc系では(null)と表示されたり、Segmentation fault (コアダンプ)したりします。


■NULL引数不可の関数は色々

#include <stdio.h>
#include <string.h>
int main(void){
    FILE *fp = NULL ;
    char buf[1024];
    memcpy(buf,0,sizeof(buf));//NG1
    if(strcmp(buf,NULL) == 0){//NG2
        fp = fopen("DEBUG","we");        
    }
    fclose(fp);//NG3
}

NG1:文脈からmemcpyではなくてmemset

NG2:NULLポインタではなくて空文字列””

NG3:fclose(NULL)はダメ。※free(NULL)は問題なし。


■類似する問題の未初期化ポインタ参照

int main(void){
    int *ptr;   //未初期化
    *ptr = 0 ;  //★NG:メモリの何処かを破壊する
}

ptr を 初期化していません。
これはメモリの何処かを
指しているかもしれないし、
指していないかもしれません。

これはNULLポインタ参照と似ていますが別の問題で
未初期化のポインタ参照と言います。

例えるならば古い葉書の住所欄が汚れている状態です。

参考:

CWE-476: NULL Pointer Dereference

EXP34-C. null ポインタを参照しない