NULLポインタの都市伝説

本文は、教科書ではありません。

信じる信じないは自己責任でお願いします。また無断転載を禁じます(筆者より)


NULLポインタとは

ここでいうNULLポインタとは、C言語における「特殊な値(一般には0)」が格納されている「ポインタ型変数」を言います。

文字列の末端記号等に代表される「値0が格納されているの領域」へのポインタ(NULLへのポインタ)とは別物ですので、混同せぬようにお願いします。

ポインタは型を付加されたアドレス

ポインタとはコンパイラによって型を付与されたアドレスです。
このアドレス値は主メモリ(もしくは周辺装置)の特定の場所を示す値ですが、「7は幸運の数字」とか「13は不吉な値」という様な、「特別な値」は存在しません。

ところがC言語では0番地を示すポインタは「特別な値」と見做され、他の値が格納されたポインタとは異なる意味で使われることがしばしばあるのです。

C言語はプログラマがアドレスを意識せずに済むように設計された言語

C言語では、ポインタ型変数への整数の直接代入は原則許されていません。C言語は「プログラマが自由にポインタ型変数の値を決められない言語」なのです。

ポインタ型の変数 = ポインタ型変数 ;
 
ポインタ型の変数 = & 普通の変数 ;

ポインタへの代入は上記の様に、他のポインタの値か、もしくは変数名にアドレス変換単項演算子(&)を前置したものだけが許されます。(注1)

(注1)もちろん、評価値がポインタとなる式も代入できます。またハードウエア的に固定されている機器のアドレスを定数として代入するなど、実際にはこれ以外にも様々なポインタへの代入方法はあります。

ポインタが示す値を格納する変数 = * ポインタ型変数 ; 

ポインタの値が幾つであれ上記の様に、値変換単項演算子(*)を使う事によりポインタが示す値を簡単に取り出すことができます。

この仕組みの導入によりC言語は、アセンブラでいう「間接アドレッシング」の操作を「アドレス」という概念なしで実現可能になりました。
より正確に言うと、C言語は「アドレスを隠蔽した構造化アセンブラ」として設計された言語であり、「アドレス」という概念が無いのは結果ではなく、元々の設計思想であったともいえます。

ポインタの値は意識する必要はないのに、何故NULLポインタがあるのか

ここからが都市伝説のはじまりです。多くの方がC言語にはNULLというポインタの特殊状態がC言語の仕様として規定されていると信じているのです。しかし、オリジナルのC言語(1972年にK&Rが著した解説本の対象となったC言語)にはNULLという名前のキーワードは存在しません。
本来、ポインタはその値が0だろうが0xFFFFFFFFだろうがそれは「意識してはいけない値」なのです。 当然「意識してはいけない値」に特殊状態などが存在する訳がありません。

ところが、少しでもC言語を学んだ経験がある方なら誰もが知っている #include <stdio.h>がこの話をややこしくしました。

本来、#include <stdio.h>は、あるライブラリ(具体的には標準入出力ライブラリ)を使うためのヘッダー宣言にすぎません。数学の問題を解くプログラマが「数値演算ライブラリ」を使ったり、グラフィック表示をしたいプログラマが「図形描画ライブラリ」を使うのと同様に、使いたい人だけが使えば良いライブラリにすぎないのです。

標準入出力ライブラリの功と罪

しかしこの標準入出力ライブラリstdio.hは、あまりにも便利かつ、どんなプログラムでも必須と言える関数を含むため、事実上C言語の一部の様な扱いになってしまいました。

「ならば、いっそのことライブラリではなくC言語の一部にするべきだったのでは?」という考える方もいるでしょうが、C言語が設計された時代は、コンピュータは装置毎に入出力の仕組みが大きく異なっていました。ですから、入出力関係の処理は、言語内に取り込まずコンピュータ毎に「標準入出力ライブラリ」を用意すると言う考えは至極当然だったのです。

ところがこの標準入出力ライブラリ、オリジナルC言語のstdio.hファイルを読んでいただければ判るが、もはや「芸術」とも言えるほどのテクニックの嵐なのです。このライブラリの製作者は無駄な変数を宣言したり、余計な引数を用意するのが大嫌いだった人だったのです。
そんな作者によって、ひとつの功とも罪とも言えるテクニックが「発明」されたのです。

具体的にはポインタ値を返す関数(例えばfopen)が、内部で何らかのエラーを起した場合にどうするか?という問題を考えれば判ります。
FORTRANやCOBOLの感覚なら、「status」とかという名前の引数を用意して、それがある値だったらエラーとする方法で実装するでしょう。ところがstdio.hの作者は「ポインタ型の関数戻り値が0ならエラー」とした(してしまった)のです。

これは、当時としては斬新かつ目からウロコの実装方法だったのに違いありません。何しろ確実に引数を1つ減らせるのだすから。
一方それにより、「0番地をアクセスして泣くソフト屋」も星の数ほど生まれることになったのです。

なぜ、エラー時の戻り値を0としたのか

では、なぜエラー時の戻り値を0としたのでしょうか?
これを正しく理解するためには、ハードウエア(コンピュータのアーキテクチャ)を若干学ぶ必要がありますが、簡単に言うと当時の多くのコンピュータは、0番地に変数を確保できないものが殆どであったからです。 

実際には1番地や2番地も変数領域には成り得ないので、1でも2でも良かったのですが、単純に考えると0を使うのが自然でした。しかし本当に0番地が変数領域に成り得ないという保証はどこにもありません。だからstdio.hの作者は0というマジックナンバーは用いずに、NULLを0にdefineして用いました。これにより、プログラマの判断で0以外の値をNULLに与える手段も用意していたのです。

だから、もしポインタ値の特殊状態として0を割り当てるのが嫌で1にしたいというなら、stdio.hの中の#define NULL 0の0を1に書き換えれば良いのです。更にポインタが特定の値を特殊な意味に使う事すら嫌ならばstdio.hの入出力ライブラリ相当を自分で作れば良いのです。

この様にK&Rの時代のC言語はアドレスが値が0のポインタを何ら特別扱いはしていないのです。NULLという言葉もNULLポインタと言う言葉も標準入出力ライブラリというローカルな仕組みの中に現われたアイデアの一つに過ぎなかったのです。

C89の功と罪

ポインタ値が0だったら特殊な意味にするという手法は瞬く間に世界中に広まりました。当時は(今もですが)、ポインタを理解するのはC言語における大きな関門でした。ですから「特殊な事態(多くの場合、何らかのエラー)の場合にNULLを返す関数を作ることができれば、ポインタを理解している」と言っても良いような状況だったとも言えます。

しかし、「ポインタ値が0なら特殊な意味とする」と「値が0のポインタは特殊だ」とでは言葉の意味が全く異なります。どの値を「特殊」とするかはプログラマの責任で決めるべき事なのです。
また論理アドレスと物理メモリアドレスの2つのアドレスを持つコンピュータが当り前になり、物理アドレスは0以外だが、論理アドレスが0という自体も生じるようになりました。他にも様々な理由や、未熟なプログラマによる誤解なども加わり、それぞれの国、それぞれの組織、それぞれの技術者により、NULLポインタの概念が異なるという混乱が生じてしまった。この混乱は C89(ANSI、ISOではC90)が採択される1990年まで続きました。

結果として、このC89で初めて、「C言語にはNULLというモノがあり、その値は0」と定められたのです。言い換えればそれ以前はNULLはstdio.hの中で#defineで定義されたローカルな値に過ぎなかったのです。

ちなみに、C89を決める国際会議が開催された時点では、多くの国や企業が既に「資産としてのC言語ソース」を数多く所有しておりました。C89の採択内容次第では、それらの資産を全て書き換えるか、さもなければ捨てるしかない状況でもあったのです。一部の企業にとっては生死を分ける会議であったと言っても過言ではなく、会議は紛糾につぐ紛糾でした。

結局C89は、純粋な技術的視点での産物ではなく、利害関係の調整という経済的視点に基づく妥協の産物であった側面は否定できません。その証拠に「NULLは0と定めるがNULLを予約語には昇格させない」という玉虫色の結果となってしまったのです。

C89で、NULLポインタの意味を限定し、それに特別な意味を持たせた事が正しかったのかどうかは、何とも言えません。
強いて言えば、C++に代表される「C言語の子供達」が値が0のポインタをどう扱っているかを見ればある程度の推測はできます。しかし、それに関する言及は別の機会とさせて頂きます。

終りに

C言語が生まれてやがて半世紀を迎える。今では自分で作ったポインタ値を返すライブラリ関数 foo() に対し、以下の様なコードを書く人も少なくありません。

#include "Foo"
        :
if(foo()==NULL){
        :
}

しかし上記の場合、本来NULLは「標準入出力ライブラリ」の中だけで使うべきなのです。
自分で作ったライブラリならば、NULLではなく、他の定数をヘッダーで宣言して使うのが「正当な歴史」なのです。

しかし何事も同じですが、昔は「正当」であったとしても、今となっては誰もその理由は覚えておらず、その時代時代ごとの「正当」が生まれるのが世の常です。
現代は#include<stdio.h>を必要としないアプリ開発も多く、「何気なくNULLと書いたらエラーとなった」と驚くプログラマもいる時代なのです。

最後になりますが、本ページ冒頭の画像にあるソースコードを見て欲しい。この記述が正しいか否かのは別として、歴史を感じて頂だけたら幸いである。

(2014/8 初出、2015/5 大幅修正)