こんにちは、ナナです。
開発の中でポインタを扱っていると「NULLポインタ」と呼ばれる言葉を耳にすることがあるでしょう。
NULLポインタを扱うことで、ポインタ変数をより安全に扱うことが可能になります。
ポインタの全貌を学びたい方は『C言語 ポインタを使いこなせ【身に付けるための9の極意】』の記事から順に読むことをお勧めします。
本記事では次の疑問点を解消する内容となっています。
では、NULLポインタを学んでいきましょう。
NULLポインタの正体
今日のテーマは「NULLポインタ」・・・なんなんですの、それは?
じいやっ「NULL」が何かを調べなさいっ!今すぐによっ!
はい、お嬢様。Wikipedia様にお伺いしたところ「NULL」とは「何もない、何も示さない」との意味のことのようです。
じいやさんのおっしゃる通り、NULLとは「何も示さない」を表すコンピュータ用語です。
NULLポインタとは「ポインタ変数が有効なメモリを参照していない」ことを示す値なのです。
このNULLポインタが、いったい何のために必要なのかを明らかにしていきましょう。
NULLポインタの定義の正体
NULLポインタとは「NULL」というキーワードを使ってプログラムします。「NULL」は多くの環境で、次のようにマクロ定義されています。
#define NULL ((void *)0)
「0」という数値を、void型ポインタで明示的キャストを行っています。void型ポインタにキャストすることで「0番地」という番地情報としているのです。
この「0番地」という番地情報こそが「NULLポインタ」の正体です。
NULLのマクロ定義値は<stdio.h>をインクルードすることで利用できるようになります。
マクロ定義は大丈夫ですかね。不安な方は次の記事を読んでください。
NULLポインタによる参照の無効化
あぁた、「何もない」ってどういうことですのっ???
わたくしにこの記事をぶつけてくるあたり、わたくしには「何もない」って言いたいわけじゃないでしょうねっ!
誤解だよ。そんなこと全く思ってないよ。
NULLポインタはポインタの参照が無効であることを示す情報なんだね。なんでそんなものが必要になるかを解説しようね。
あぶないっ!弓矢をこっちに向けないで!
ポインタ変数のイメージとは「弓矢」でした。弓矢であるがゆえに、扱いには注意が必要です。
ポインタ変数は、別の変数を遠方から読み書きするための機能です。
「ポインタ変数」といっても変数の一種ですから、変数定義をすることでポインタ変数もメモリ上に存在することになります。
// ポインタ変数の定義
long * pnum;
変数である以上、ポインタ変数はメモリ上に配置された瞬間から、何かしらの数値を必ず持つことになります。意図した数値かどうかは関係なく、必ず数値を持つのです。
もしも、ポインタ変数を初期化をしていないのであれば、その番地は偶発的な数値になっていることでしょう。
// 未初期化のポインタ
long * pnum;
// ポインタ参照先メモリに値を書き込み!
// 不正な書き込み!
*pnum = 0x01;
上図の例では、ポインタ変数pnumが保有する「0x3981A9番地」は偶発的な番地であり、ポインタは不正なメモリ番地を偶然ながら参照していることになります。
よって、この番地に対する読み書きは絶対にやってはなりません!
照準を設定していないポインタとは、適当な方向に弓矢を射る行為と等しいのです。
もし誤って不正なメモリに対する値を読み書きしてしまったら、プログラムは何かしら問題を起こすことでしょう。
NULLポインタは照準が無効であることを表すデータ
メモリの番地とは0番地からメモリの搭載分だけ番地が存在します。
問題は、ポインタの番地の数値を見ても正当なメモリ番地なのか、不正なメモリ番地なのかが判断ができないことなのです。
この問題を解決するのが「NULLポインタ」です。
「NULLポインタ」はポインタ変数の参照先が、「無効」であることを示す唯一の数値なんです。
ポインタ変数の中身が、もしNULLであった場合は「その参照は無効である」ということを示します。
つまり、ポインタ変数の参照先としてNULL(0番地)は正当なメモリとして利用されることはないということです。
プログラムでは次のように使用します。
long num1 = 100;
long * pnum = NULL; // pnum --> NULL(参照無効)
・・・処理・・・
// 正当なメモリ番地を設定
pnum = &num1; // pnum --> num1
*pnum = 0x01;
ポインタ変数「pnum」を定義してから、num1の番地を設定するまでの間は「NULLポインタ」を設定しておきます。
こうしておくことで、ポインタ変数の参照が一時的に無効であることを明示しておくのです。
ポインタ変数にちゃんとしたメモリ番地を設定するまでの間は、「NULL」を設定しておくんです。このルールを徹底するんです。
NULLポインタを使った「NULLチェック」とは
NULLポインタの存在意義はわかりましたわ。でも、使い方がわかりませんわっ!どんな時に利用するのかをお教えなさいっ!
その通りだね。NULLをポインタに設定したところで、それを有効活用しなかったら意味がないよね。それじゃあ、「NULLチェック」の仕組みを学ぼうね。
NULLポインタというものが存在する理由は、「NULLチェック」と呼ばれる判定を行うためです。
NULLチェックとはいったい何なのかを解説していきましょう。
NULLチェックを使ったポインタの無効判定
ポインタ変数の参照が「無効」か「有効」かは、次のプログラムのようにチェックを行います。
関数の引数にポインタ変数が登場するケースでは、渡された番地の正当性をチェックするためによく実施されます。
int subfunc(long * pnum)
{
// NULLチェック
if (pnum == NULL)
{
// エラーを表示して処理中止
printf("ERR");
return -1;
}
・・・処理・・・
}
ポインタ変数が保有する番地が「NULLポインタ」であるかを確認するこの処理のことを、通称「NULLチェック」と呼びます。
ポインタ変数の番地の正当性は、このNULLチェックでしか判断できません。
つまり、ポインタ変数の値は「NULLであれば参照が無効」、「NULL以外であれば、正当なメモリへの番地」として扱うようにします。
そして、ポインタ変数がNULLか否かによって、番地の正当性を判断できるようにしておくのです。
NULLチェックは関数の引数に対して行ったり、他の関数から取得したポインタが正当なものかを判断するときによく登場します。
NULLチェックによりポインタの参照が「有効」であることを確認できることで、プログラムをより安全に動かすことができるのです。
NULLポインタとヌル文字は違う!
わたくし、前に文字列を学んだ時に「ヌル文字」を習得しましたわ!「NULLポインタ」って名前が似てますわね。親戚なのかしらっ?
この2つを同じものと思っている方がいますが、これ違うんですよ。全然別の意味なんです。親戚でもなんでもありませんよ。
C言語初心者の方は、この2つを同じものと勘違いしがちです。全く別の意味ですので注意しましょう。
NULLポインタとヌル文字の違い
『C言語 文字と文字列を図解【何が違うのこの2つ?解決します】』の記事にて「ヌル文字」という言葉が出てきました。
名前が似ているため、「NULLポインタ」と「ヌル文字」を同じものであると思っている方がいますが、これは間違いです。
各言葉の意味は次のものになります。
NULLポインタ
ポインタ変数の参照先が無効であることを示す値のこと。プログラム上では「NULL」として表記する。
ヌル文字
文字列の終端を示す値のこと。プログラム上では「’\0’」として表記する。
NULLという言葉が単独で使われた時は、一般的に「NULLポインタ」のことを示します。
NULLポインタとヌル文字の違いを知るためのサンプルコード
では、「NULLポインタ」と「ヌル文字」の、正しい使い方と間違った使い方をプログラムで示しましょう。
正しい使い方
#include <stdio.h>
int main(void)
{
char moji[10];
long * pnum;
// char型の配列要素にヌル文字を設定
moji[0] = '\0';
// ポインタ参照先にNULLポインタを設定
pnum = NULL;
return 0;
}
間違った使い方
#include <stdio.h>
int main(void)
{
char moji[10];
long * pnum;
// char型に配列要素にNULLポインタを設定
moji[0] = NULL;
// ポインタ参照先にヌル文字を設定
pnum = '\0';
return 0;
}
ややこしいことに「NULLポインタ」と「ヌル文字」は、コンピュータ上の数値としては共に「0」で管理されています。
そのため、誤って使っても辛うじて動作します。
しかし、皆さんは使い方を間違えなようにしましょう。
間違ったプログラムを作っていると、C言語に関する知識のなさを周りに宣伝するようなものです。
技術者とは信頼を受けて仕事をしますので、このようなプログラムを書いていては信頼を失ってしまうのです。