warning: dereference of NULL
警告:NULL ポインタ参照
警告:NULL ポインタの逆参照
警告:NULL ポインタの間接参照
※上記3警告は日本では皆同じ意味で、
「NULLポインタの指す先を参照する」の意味です。
[-Wanalyzer-null-dereference]
■無効なポインタアクセス(Invalid Pointer Access)
#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です。
■戻り値をNULL判定しないのは手抜き
#include <malloc.h>
#include <string.h>
int main(void) {
//戻り値をNULL判定せずにいきなり書き込み
int *data = malloc(1024);
data[0] = '\0' ;//NG
free(data);
//戻り値をNULL判定せずにいきなり関数引数
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ポインタ参照と似ていますが別の問題で
未初期化のポインタ参照と言います。
例えるならば古い葉書の住所欄が汚れている状態です。
参考: