C言語でポインタは非常に便利ですが、ポインタの状態によってはポインタの先にアクセスすることが危険な時があります。
これを防ぐのに非常に重要な役割を果たすのが「NULL
」です。このページではまず NULL
について解説し、その後ポインタの4つの状態と、安全なポインタの使い方を解説したいと思います。
Contents
NULL
とは
NULL
とは一般的には何もない空の状態を指す言葉です。
特にC言語においてはポインタが NULL
を指すことで、そのポインタがどこも指していない状態を明示的に表す目的で使用されます。
具体的には、下記のように NULL
が代入された p1
はどこも指していない状態として扱われます。逆に p2
は変数 a
のアドレスが格納されているので、変数 a
を指している状態となります。
int *p1;
int *p2;
int a = 100;
p1 = NULL;
p2 = &a;
各変数の状態を図で表すと下のようになります。
では NULL
って実際に何なのでしょうか?これは printf
を使って表示してみると分かります。
printf("NULL = %p\n", NULL);
printf
での表示結果は下記のようになりました。
おそらくほとんどの方は同じ結果になると思います。環境によってはもしかしたら違う値になるかもしれません。
NULL = 0x0
つまり、NULL
は実際には単に 0
番地を表すアドレスの定数です。
通常ユーザーが作成するようなプログラムにおいては 0
番地のメモリは使用されませんので(OSの起動時等に用いられる)、そのアドレス 0
を便宜的に「どこも指していない」を表すアドレスとして扱っているのです。
NULL
の意味としては、定義値の 0
よりも、「どこも指していない」を表すものとして捉えた方が良いです。
ですので、「NULL
= どこも指していない」で覚えておきましょう!
この NULL
はポインタを扱う上で非常に重要な定数となります。
これについては次のポインタの状態で解説していきたいと思います。
ポインタの状態
続いてC言語のポインタが取りうる「4つの状態」について解説します。
スポンサーリンク
不定アドレスを指している状態
1つ目の状態は不定アドレスを指している状態です。
これは、変数宣言直後のポインタの状態になります。
変数は、変数宣言直後には不定値が格納されています。つまり、どんな値が格納されていない状態です。
これは、ポインタ変数も同様です。つまり、ポインタは変数宣言直後ではどこを指しているかわからない状態です。
これがポインタの状態の1つ目の「不定アドレスを指している状態」です。
int *p;
この状態を図示すると下記のようになります。
確保したメモリを指している状態
2つ目の状態は確保したメモリを指している状態です。
変数に &
演算子を付けてアドレスを取得し、それをポインタ変数に格納すると、ポインタはそのプログラム内で確保したメモリを指している状態になります(プログラムで使用可能なメモリを指している状態)。
また malloc
関数の戻り値をポインタ変数に格納するのも同じで、これも確保したメモリを指している状態になります。
p = (int*)malloc(sizeof(int) * N);
この状態を図示すると下記のようになります。
解放したメモリを指している状態
3つ目の状態は解放したメモリを指している状態です。
malloc
関数で確保したメモリを指しているポインタを free
関数で解放すると、そのポインタの指すメモリが解放されます。
これにより、free
関数に指定したポインタは、解放したメモリを指している状態になります。
free(p);
この状態を図示すると下記のようになります。
スポンサーリンク
NULL
を指している状態
4つ目の状態は NULL
を指している状態です。
前述の通り、NULL
というのは「このポインタはどこも指していないですよ」というのを明示的に表す定数です。
ポインタに NULL
をセットすると、そのポインタは NULL
を指している状態になります。
p = NULL;
この状態を図示すると下記のようになります。
具体的に下記のプログラムでポインタの状態を色で示してみました。
各色に対応するポインタの状態は下記のようになります。
- 緑:不定アドレスを指している状態
- 青:確保したメモリを指している状態
- 黄:解放したメモリを指している状態
- ピンク:
NULL
を指している状態
安全にアクセス可能な状態
この4つの状態のうち、ポインタが指している先に安全にアクセスできるのは「確保したメモリを指している状態」のみです。
他の3つの状態では、ポインタで指しているだけであれば問題ありませんが、安全にアクセスすることはできません。
そのポインタの指すアドレスの値を取得しようとしたり、値を変更しようとしたりするとセグメンテーションフォールトが発生したり NULL
ポインタアクセスが発生したりしてプログラムが落ちる可能性があります。
プログラムで判断できる状態
しかしその一方で、C言語のプログラム内ではポインタの下記の2つの状態しか判断することができません。
- ポインタが
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つに減らすことができ、安全にポインタを使用することができるようになります。
ですので、ポインタを使用する際には、下記の3つを心がけるようにしましょう!(私のプログラムでもできていないことが多いですが…)
- 変数宣言時に必ず
NULL
で初期化する free
直後に必ずポインタにNULL
を代入する- ポインタの指す先にアクセスする前に
NULL
かどうかを判断する(NULL
チェック)
是非日頃のプログラミングでこの3つを心がけ、癖のように NULL
代入と NULL
チェックをするようにしましょう。
ただし、上記を行えば必ず安全なアドレスにアクセスできるようになるというわけではないです。具体的には下記の場合は危険なアドレスにアクセスする可能性があります。
- ポインタにアドレスを直値で設定する
- ポインタに加減算を行なったアドレスにアクセスする
前者に関しては、OS やデバイスドライバなどのアドレスを直で指定して動作させるプログラムを開発したりしない限りはやらないと思います(ポインタでアクセスしていいのは、変数や malloc
等で確保したメモリのアドレスだけ!)。
後者に関しては、配列や malloc
で確保した大きなメモリをアクセスする時には結構行います。正直これに関しては、ポインタが指す先のメモリのサイズ(変数のサイズや malloc
で確保したメモリのサイズ)を超えたアドレスにアクセスしないように、ポインタに加減算を行うように心がけるしかありません。
ただ、逆にいうと、下記の3つをしっかり実行していれば、セグメンテーションフォールトなどが発生した場合の原因は「ポインタへの加減算」と断定できるので、デバッグはやはりやりやすくなると思います!
- 変数宣言時に必ず
NULL
で初期化する free
直後に必ずポインタにNULL
を代入する- ポインタの指す先にアクセスする前に
NULL
かどうかを判断する(NULL
チェック)
まとめ
このページでは NULL
とポインタの4つの状態について解説しました。
ポインタを扱う上で、ポインタの状態を下記の2つに限定することが、ポインタを安全に使用するポイントになります。
- 確保したメモリを指している状態
NULL
を指している状態
ぜひ日頃からポインタ変数宣言時の NULL
初期化と free
関数直後の NULL
代入を行いうようにし、ポインタの状態を2つに限定することを心がけるようにしてください!
オススメの参考書(PR)
C言語一通り勉強したけど「ポインタがよく分からない」「ポインタの理解があやふや」「もっとC言語の理解を深めたい」という方には、下記の「C言語ポインタ完全制覇」がオススメです!
この本の主な内容は下記の通りで、通常の参考書では50ページくらいで解説するポインタを、この本では約 "360ページ" 使って幅広く・深く解説しています。
- C言語でのメモリの使い方
- 配列とポインタの関係性
- ポインタのよくある使い方
- ポインタの効果的な使い方
一通りC言語を学んだだけだと "理解があやふやになってしまいがち" "疑問に思いがち" な内容に対する明確な解説が多いため、特にポインタやC言語の理解があやふやという方にはオススメの本です。
また、C言語においてポインタはまさに "肝" となる機能ですので、ポインタについてより深く学ぶことでC言語全体の理解を深めることにもつながります。
ポインタ・C言語についてより深く理解するための本としては現状1番のオススメの本です。
ただし、他の入門書等で "一通りC言語を学んでいる" 方向けの解説になっているので、"C言語を始めるにあたっての最初の入門書" として利用すると難易度が高いので注意してください。
入門用のオススメ参考書は下記ページで紹介していますので、こちらも是非参考にしていただければと思います。
https://daeudaeu.com/c_reference_book/