ポインタと参照
値そのものではなく、値が置かれたメモリのアドレスを指し示す仕組みです。大きなデータを安く受け渡せる反面、ヌル参照やダングリングといった事故の温床にもなります。
- 1.ポインタ・参照は値の実体ではなく、その値が格納されたメモリのアドレスを指す仕組みです。
- 2.値渡しは中身を複製し、参照渡しは同じ実体を共有するため、呼び出し先での変更が呼び出し元へ波及します。
- 3.無効なアドレスを指すヌル参照やダングリングは、クラッシュや未定義動作を招く代表的な危険です。
ポインタと参照とは
変数は、メモリ上のどこかに値を置いた「箱」です。ポインタや参照は、その箱の中身そのものではなく、**箱が置かれている場所(アドレス)**を保持する仕組みを指します。住所が書かれた付箋をイメージすると分かりやすく、付箋自体は小さくても、たどれば本体の家にアクセスできます。
- 間接アクセス — 値を直接持つのではなく、アドレス経由で本体を読み書きします。
- 共有 — 同じアドレスを複数の変数が指せるため、1つの実体を皆で参照できます。
- 軽い受け渡し — 巨大なデータでも、コピーせずアドレス(数バイト)だけを渡せます。
C/C++ の「ポインタ」は数値としてのアドレスを直接扱える強力な機能で、&(アドレス取得)や *(参照先の取得=デリファレンス)で操作します。一方 Java や Python の「参照」は、アドレス演算を禁止し、本体を安全に指すことに用途を絞った仕組みです。
int x = 10;
int *p = &x; // p は x のアドレスを保持
*p = 20; // p の指す先(=x)を書き換える
// この時点で x は 20 になっている
値渡しと参照渡し
関数に引数を渡すとき、「実体を複製して渡す」のか「実体を共有する」のかで挙動が大きく変わります。これが値渡し(pass by value)と参照渡し(pass by reference)の違いです。
| 観点 | 値渡し | 参照渡し |
|---|---|---|
| 渡るもの | 値のコピー | 実体を指すアドレス |
| 呼び出し先での変更 | 呼び出し元に影響しない | 呼び出し元にも波及する |
| コスト | 大きいデータほど重い | 常に軽い(アドレスのみ) |
| 主な用途 | 安全に独立して使いたい | 本体を更新・大きいデータ |
値渡しでは関数内の変更は手元のコピーに閉じますが、参照渡しでは呼び出し元の変数まで書き換わります。注意したいのは、多くの言語(Java や JavaScript、Python)が採用する「参照の値渡し」です。これは「アドレスという値をコピーして渡す」方式で、指し先のオブジェクトの中身は変更できますが、引数自体に別の実体を再代入しても呼び出し元には反映されません。
function update(arr) {
arr.push(4); // 指し先の配列を変更 → 呼び出し元に反映される
arr = [9, 9]; // 別の配列を再代入 → これは呼び出し元に反映されない
}
const list = [1, 2, 3];
update(list);
// list は [1, 2, 3, 4](再代入は効かないが push は効く)
ヌル参照という危険
参照は「どこも指していない」状態を表すために、null(あるいは nullptr、None、nil)という特別な値を取れます。問題は、何も指していない参照の先を読み書きしようとした瞬間に発生します。
- 多くの言語でヌル参照のデリファレンスはクラッシュを招きます(
NullPointerException、セグメンテーション違反など)。 - 「値があるはず」という暗黙の前提が崩れる場所で頻発し、実行するまで気づきにくいのが厄介です。
- 発明者の C.A.R. ホーア自身が、後にこれを「10億ドルの過ち」と呼んだほど、被害の大きいバグの源です。
参照を受け取ったら、デリファレンス前に null でないかを確認するのが基本です。近年の言語は、null を取り得る型と取り得ない型を分けてコンパイル時に検査する仕組み(Kotlin の ?、Swift の Optional、Rust の Option、TypeScript の strictNullChecks)を備え、ヌル参照を型のレベルで封じ込めようとしています。
ダングリングと無効化
もう一つの代表的な事故がダングリングポインタです。これは、すでに解放された(あるいは寿命が尽きた)メモリを、まだ参照が指し続けている状態を指します。付箋は残っているのに、家がもう取り壊されているイメージです。
int *p;
{
int local = 5;
p = &local; // ローカル変数のアドレスを保持
} // ここで local の寿命が終わる
// 以降 *p は解放済み領域を指す=ダングリング(未定義動作)
- 解放済み領域への読み書きは未定義動作で、運が悪いと別データを破壊します。
- 同じ領域を**二重に解放(double free)**するのも、クラッシュやセキュリティ脆弱性の原因になります。
- 解放後に
NULLを代入しておく、所有者を一意にするなど、規律ある管理が予防策になります。
これらの危険は「誰がいつメモリを解放するか」が曖昧なことに起因します。Java や Go はガベージコレクションで到達不能になった領域を自動回収し、ダングリングを構造的に防ぎます。Rust は所有権と借用の規則でコンパイル時に寿命を検査し、GC なしでダングリングや二重解放を排除します。
まとめ
ポインタと参照は、値の実体ではなくそのアドレスを指す仕組みで、共有と軽い受け渡しを可能にします。引数の渡し方では、複製する値渡しと実体を共有する参照渡しの違いが挙動を左右し、多くの言語は「参照の値渡し」という中間的な方式を採ります。一方で、何も指さないヌル参照や、寿命の尽きた領域を指すダングリングは深刻なバグの源です。これらは型システムやガベージコレクション、所有権モデルといった仕組みで、近年ますます安全に扱えるようになっています。
プログラミング Article
ポインタと参照を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
ポインタ
比較で見る軸
難易度: intermediate / カテゴリ: プログラミング / タグ数: 3
導入後に効く点
値渡しは中身を複製し、参照渡しは同じ実体を共有するため、呼び出し先での変更が呼び出し元へ波及します。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- intermediate
- カテゴリ
- プログラミング
- タグ数
- 3
判断チェックリスト
- 自社の用途が「ポインタ / メモリ」に近いか確認する。
- 強みである「ポインタ・参照は値の実体ではなく、その値が格納されたメモリのアドレスを指す仕組みです。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。