warning: dereference of NULL
警告:NULL ポインタの関節参照
[-Wanalyzer-null-dereference]
■1.未初期化ポインタ参照
int main(void){
int *ptr; //未初期化
*ptr = 0 ; //★NG:メモリの何処かを破壊する
}
ptr を 初期化していません。
これはメモリの何処かを
指しているかもしれないし、
指していないかもしれません。
これはNULLポインタ参照と似ていますが別の問題で
未初期化のポインタ参照と言います。
例えるならば古い葉書の住所欄が汚れている状態です。
■2.問題例1(簡単な問題)
#include <stdio.h>
int main(void){
int *ptr = NULL;
*ptr = 0; //★NG:NULLポインタ参照が発生する
}
ptr を NULLで初期化していますが、
これはまだメモリの何処も指していない状態です。
これをNULLポインタと言います。
例えるならば葉書の住所欄がマッサラの状態です。
NULLポインタの指す先を参照すると
多くの場合プログラムは異常終了します。
■3.修正例
int main(void){
int x;
int *ptr = &x;
*ptr = 0; //OK:ポインタは変数xを参照してる
}
ptr は変数 x のアドレスを指しています。
例えるならば葉書の住所欄がしっかり記入されている状態です。
■4.問題例2(関数跨ぎ)
#include <stdio.h>
void f(int *p) {
*p = 1 ;//★NG:関数跨ぎのNULL参照
}
int main(void) {
f(NULL) ;
}
NULLポインタは引数で渡ってくる場合が多いので、
関数を跨いだり、ファイルを跨いだりした場合に
問題検出は指数的に難しくなってきます。
残念ながら、この問題は
gcc/clang -Wall -Wextra では検出できません。
※最近追加された gcc -fanalyzer オプションを使うと検出できます。
■5.誤った対策(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チェックしても意味がありません。
※交通事故を起こした後で左右確認しても意味がないのと同じです。
■6.誤った対策(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です。
■7.問題例3(戻り値未確認)
#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を返すかもしれないので戻り値を確認してください。
■8.問題例4(書式%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 (コアダンプ)したりします。
■9.問題例5(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)は問題なし。
参考: