アドレスとポインタ
多くのプログラミング学習者にとって、C言語などの「ポインタ」は最初の挫折ポイントとして知られています。しかし、これまで学んできた「メモリ」の物理構造を頭に入れた上でアプローチすれば、ポインタは決して難しい概念ではありません。メモリの「番地(アドレス)」と「データのコピー」という観点から、ポインタの本質を解き明かします。
1. メモリの「住所」= アドレス
第1章4節で学んだように、メモリ(RAM)は巨大な「電球(ビット)の並び」であり、8個のビットをまとめた「1バイト」を最小単位としてデータを読み書きします。
この無数に並んだ1バイトずつの「部屋」を区別するために、すべての部屋には0番から順番に番号が振られています。この番号のことをメモリのアドレス(Address:番地)と呼びます。
住所がわかれば、郵便局員が手紙を届けられるように、CPUも指定されたアドレスに対してピンポイントでデータを書き込んだり、読み出したりできます。
2. ポインタ(Pointer)とは?
プログラミングにおいて、通常の変数は「数値(42 や 99)」などを値として保持します。
これに対し、「別のデータが置いてあるメモリのアドレス(番地)そのものを値として保持する変数」のことをポインタ(Pointer)と呼びます。 「ポイント(指し示す)するもの」という名が表す通り、ポインタはメモリの特定の場所を指し示す矢印の役割を果たします。
ポインタの仕組み自体はこれだけです。「ただのアドレス数値が入っている変数」に過ぎません。
3. なぜポインタを使うのか?(コピーコストの削減)
「わざわざアドレスを経由せず、データを直接やり取りすればいいのではないか」と思うかもしれません。 ポインタを使う最大の理由は、「データをやり取りする際のコピーコストを削減し、動作を高速にするため」です。
例えば、あなたが撮った容量が数GBもある「超高画質な旅行の動画」を、友人に渡したいとします。
- 値渡し(丸ごとコピー): 動画データを丸ごとダビング(コピー)して友人に渡す。コピーに多大な時間とストレージ容量がかかります。
- ポインタ渡し(アドレスだけ渡す): 動画データが保存されている「クラウド上のURL(保存場所)」だけをメモに書いて友人に手渡す。手渡すメモはわずか数バイトで、瞬時に完了します。
高水準言語で関数を呼び出す際、非常に大きなデータ(何万行もある顧客リストなど)を引数として渡す場合、データ本体をそのまま渡すと、コンピュータはメモリ上でその巨大なデータの「丸ごとコピー」を作成するため、メモリを浪費し動作が極端に遅くなります。
しかし、データの「先頭アドレス(ポインタ)」だけを渡せば、たとえ本体が数ギガバイトあっても、やり取りされるのはアドレスの数値(32ビットまたは64ビット=わずか数バイト)だけなので、一瞬で処理が完了します。
4. ヌルポインタと「クラッシュ」の恐怖
ポインタは非常に強力で効率的ですが、使い方を誤るとプログラムを即座にクラッシュさせる危険な武器にもなります。
ポインタ変数が「どこも指していない」状態を表すために、特別な値 0(または NULL, nullptr)が代入されたポインタのことをヌルポインタ(Null Pointer)と呼びます。
もしプログラムが、この「存在しない場所(ヌルポインタ)」のデータを読み書きしようとすると、OSはメモリ保護違反を検出し、そのプログラムを強制終了(クラッシュ)させます。これが有名なヌルポインタアクセス違反(ヌルポインタ例外)です。
「10億ドルの誤り」
ポインタ(あるいは参照)に「どこも指していない無効な状態」としてヌル値(NULL)を導入したアントニー・ホーア(クイックソートの発明者でもある著名な計算機科学者)は、後年に「NULLの導入は、無数のエラーやシステムクラッシュを引き起こし、過去数十年にわたって10億ドル以上の損失をもたらした、私の最大の誤りだった」と述懐しています。そのため、現代の多くの新しい言語(Swift, Kotlin, Rustなど)では、安全性の観点からNULL(ヌルポインタ)を原則として排除、あるいは厳格にチェックする設計が採用されています。
第3章を通じて、プログラムが翻訳される過程から、メモリの効率的な使い方(階層構造、スタックとヒープ、ポインタ)まで、ソフトウェアを実行する「土台」について学びました。
次の第4章では、このプログラムやメモリが互いに衝突することなく、複数のアプリが同時に安全に動くことができるようにハードウェアを統制する「オペレーティングシステム(OS)」の役割について学びます。