徹底図解!「NULL」と「ポインタの安全な使い方」を解説

C言語でポインタは非常に便利ですが、ポインタの状態によってはポインタの先にアクセスすることが危険な時があります。これを防ぐのに非常に重要な役割を果たすのが「NULL」です。このページではまず「NULL」について解説し、その後ポインタの4つの状態と、安全なポインタの使い方を解説したいと思います。

NULLとは

NULL とは一般的には何もない空の状態を指す言葉です。

特にC言語においてはポインタが NULL を指すことで、そのポインタがどこも指していない状態を明示的に表す目的で使用されます。

具体的には、下記のように NULL が代入された p1 はどこも指していない状態として扱われます。逆に p2 は変数 a のアドレスが格納されているので、変数 a を指している状態となります。

int *p1;
int *p2;
int a = 100;
p1 = NULL;
p2 = &a;

図で表すと下のようになります。

ポインタが NULL を指している状態

では NULL って実際に何なのでしょうか?これは printf を使って表示してみると分かります。

printf("NULL = %p\n", NULL);

表示結果は下記のようになりました。おそらくほとんどの方は同じ結果になると思います。環境によってはもしかしたら違う値になるかもしれません。

NULL = 0x0

つまり、NULL は実際には単に 0x0 番地を表すアドレスの定数です。通常ユーザーが作成するようなプログラムにおいては 0x0 番地のメモリは使用されませんので(OSの起動時等に用いられる)、そのアドレス 0x0 を便宜的に「どこも指していない」を表すアドレスとして扱っているのです。

NULL の意味としては、定義値の 0x0 よりも、「どこも指していない」を表すものとして捉えた方が良いので、「NULL = どこも指していない」で覚えておきましょう!

この NULL はポインタを扱う上で非常に重要な定数となります。これについては次のポインタの状態で解説していきたいと思います。

ポインタの状態

続いてC言語のポインタが取りうる4つの状態について解説します。

不定アドレスを指している状態

1つ目の状態は不定アドレスを指している状態です。

ポインタ(他の変数もだけど)は変数宣言されると、その変数に値を格納できるようにメモリが確保されます。さらに、そのメモリには、宣言直後は不定値が格納されています。ポインタで考えると、どこを指しているかわからない状態です。これがポインタの状態の1つ目の不定アドレスを指している状態です。

int *p;

図示すると下記のようになります。

確保したメモリを指している状態

2つ目の状態は確保したメモリを指している状態です。

変数に「&」演算子を付けてアドレスを取得し、それをポインタ変数に格納すると、ポインタはそのプログラム内で確保したメモリを指している状態になります。

また malloc 関数の戻り値をポインタ変数に格納するのも同じで、これも確保したメモリを指している状態になります。

p = (int*)malloc(sizeof(int) * N);

図示すると下記のようになります。 

解放したメモリを指している状態

3つ目の状態は解放したメモリを指している状態です。

malloc 関数で確保したメモリを指しているポインタを free 関数で解放すると、そのポインタは解放したメモリを指している状態になります。

free(p);

図示すると下記のようになります。 

NULLを指している状態

4つ目の状態は NULL を指している状態です。

前述の通り、NULL というのはこのポインタはどこも指していないですよ、というのを明示的に表す定数です。ポインタに NULL をセットすると、そのポインタは NULL を指している状態になります。

p = NULL

図示すると下記のようになります。 

具体的に下記のプログラムでポインタ p の状態を色で示してみました。緑が不定アドレスを指している状態、青が確保したメモリを指している状態、黄が解放したメモリを指している状態、ピンクがNULLを指している状態です。

スポンサーリンク

安全にアクセス可能な状態

この4つの状態のうち、ポインタが指している先に安全にアクセスできるのは「確保したメモリを指している状態」のみです。

他の3つの状態では、ポインタで指しているだけであれば問題ありませんが、安全にアクセスすることはできません。そのポインタの指すアドレスの値を取得しようとしたり、値を変更しようとしたりするとセグメンテーションフォールトが発生したりNULLポインタアクセスが発生したりしてプログラムが落ちる可能性があります。

プログラムで判断できる状態

しかしその一方で、C言語のプログラム内では、基本的にポインタがNULLか、それ以外であるかどうかの判断しかできません。つまり、NULLかどうかは分かるのですが、NULL以外の場合に、そのポインタが安全にアクセス可能な「確保したメモリを指している状態」であるか、それともアクセスが危険である「不定アドレスを指している状態」 or 「解放したメモリを指している状態」かは判断ができないのです。

例えば下記のようなプログラムだとポインタがNULLの時はポインタの指す先にアクセスしないように制御することができますが、そのポインタが「確保したメモリを指している状態」であるかどうかを判断することができません。NULLではないからといってアクセスできるかどうかは分からないのです。

if(p != NULL){
    printf("%d\n", *p);
}

安全にポインタを使用する方法

ここまで解説してきた内容を簡単にまとめます。

  • 安全にポインタの指す先にアクセスできるのは、「確保したメモリを指している状態」のみ
  • プログラムで判断できるのは、「NULLを指している状態」か「それ以外」かのみ

つまり、状態が4つある以上、C言語プログラムでポインタの指す先に安全にアクセスできるかを保証することは不可能なのです。

ではポインタは安全に使用することはできないのか?と言うとそうではありません。下記の2つさえ確実に行えば、ポインタの状態を「確保したメモリを指している状態」と「NULLを指している状態」の2つに限定することができ、安全にポインタを使用することが可能です。

  • 「不定アドレスを指している状態」になった瞬間にポインタにNULLを指させる
  • 「解放したアドレスを指している状態」になった瞬間にポインタにNULLを指させる

つまり、アクセスもできない、NULL判断もできない状態になった瞬間にポインタにNULLを代入するのです。これにより状態を「確保したメモリを指している状態」と「NULLを指している状態」に限定することができ、ポインタを安全に扱うことができるようになるのです。

プログラム的に言うと、ポインタ変数宣言時に必ずNULLで初期化すること、

int *p = NULL;

と、free直後に必ずポインタにNULLを代入すること、

free(p);
p = NULL;

この2つにより状態を2つに減らすことができ、安全にポインタを使用することができるようになります。

ですので、ポインタを使用する際は変数宣言時に必ずNULLで初期化することと、free直後に必ずポインタにNULLを代入することを心がけるようにしてください(私のプログラムでもできていないことが多いですが…)。是非日頃のプログラミングでこの2つを心がけ、癖のようにNULL代入するようにしましょう。

スポンサーリンク

まとめ

このページでは NULL とポインタの4つの状態について解説しました。ポインタを扱う上で、状態を「確保したメモリを指している状態」と「NULLを指している状態」の2つに限定することが安全にポインタを使用する上でのポイントになります。

ぜひ日頃からポインタ変数宣言時のNULL初期化とfree関数直後のNULL代入を行い、ポインタの状態を2つに限定することを心がけるようにしてください!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です