エンジニアリング101:ポインタを理解する

エンジニアリング101:ポインタを理解する

PythonやJavaScriptから始めた人にとって、ポインタは白ひげのシステムエンジニアだけが扱う謎の魔法に聞こえる。大学でC++を学んだ人には、セグメンテーションフォールトやメモリリークと格闘した記憶がある。

秘密はこうだ。ポインタはただの変数だ。その値は数字で、別の何かのアドレスを指している。

ポインタを理解するには、まずデータがどこに住んでいるかを知る必要がある。

二つの領域:スタックとヒープ

コードが動くとき、データはどこかに置かれる。それはスタックかヒープのどちらかに落ち着く。

スタック:速くて整然としている

スタックは机の上の付箋の束を想像しよう。

int a = 10; という変数があれば、たいていここに置かれる。速くて安全だ。

ヒープ:ワイルドウェスト

ヒープは巨大な倉庫だ。

では、ポインタとは何か

ポインタは単なる道しるべだ。

score = 99 という変数がヒープ(アドレス 0x1234)にあるとする。ポインタはスタック上の小さな変数で、「データは 0x1234 にある」と指し示す。

int score = 99;  // 実際の値
int *p = &score; // ポインタ 'p' は 'score' のアドレスを保持する

p はアドレスだ。データがどこにあるかを示す。*p は値、つまりそのアドレスに格納されているものだ。

なぜこんな苦労をするのか

値をそのまま渡して、言語に後始末を任せればいいのでは?

1. 速さ(コピーしない)

10MBの画像を想像しよう。それを process(image) という関数に渡して、言語が画像をコピーすると、ピクセルをコピーするのに10MBのRAMとCPU時間を無駄にする。ポインタならアドレス(8バイト)を渡すだけだ。関数は画像の場所を知って、そこを見に行く。

2. 状態を共有する

変数を値渡しすると、関数はクローンを受け取る。クローンを変えても元はそのまま。ポインタを渡せば、関数は元の場所を知る。実際のデータを変更できる。

3. 動的な構造

連結リスト、木、グラフは、別のデータの塊へのポインタを持つデータの塊だ。参照なしにこれらを組み立てるのは難しい。

支払う代償

ポインタは無料ではない。

  1. メモリのオーバーヘッド: ポインタ自体が場所を取る(64ビットシステムではたいてい8バイト)。小さな整数へのポインタの配列なら、データ本体よりポインタの方がメモリを食うこともある。
  2. パフォーマンスの低下: 「デリファレンス」(ポインタを辿ってデータにたどり着くこと)にCPUサイクルがかかる。また「キャッシュミス」を起こすこともある。データがRAMの遠い場所にあれば、CPUはそれを取り出すのを待たなければならない。スタックはキャッシュに優しい。連結リストのようなポインタだらけの構造は、たいていそうではない。
  3. 頭脳の負担: ヌルポインタ例外。ダングリングポインタ。メモリリーク。ポインタを手で管理するには規律が要る。だからRustやGoのような現代の言語、あるいはJavaやV8のようなランタイムは、ポインタを隠したり管理したりしようとする。

結論

ポインタはコードとハードウェアの橋だ。高水準のJavaScriptやPythonを書いているときでも、触っているオブジェクトは裏では参照だ。だから a = b は a を b と同じデータに向ける。

© Melvin Laplanche - All rights reserved.