LWP | C言語とJavaのNULLとnullを4つの層で理解する
LWP TECHNICAL ARTICLE | 196
C言語とJavaのNULLとnullを4つの層で理解する
~コイ丸先輩とシャケもんが、言語仕様・コンパイラー・実行環境の違いを4枚で解説~
Copyright © 2026 LWP 山中 一弘
本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。
記事要約
NULLポインターを「0番地を指す特別な値」とだけ説明すると、数値の0、仮想アドレス0、C言語のnull pointer、Javaのnullという別々の概念が混ざってしまいます。
この記事では、コイ丸先輩とシャケもんの4枚の掛け合いインフォグラフィックを中心に、言語仕様、コンパイラー、実行形式、CPU・MMU・OSという層を分けます。最も重要な結論は、CとJavaで内部実装に似た技法が使われることがあっても、null参照後の結果について言語が結ぶ「契約」は異なるという点です。
本記事の対象とゴール
想定読者
C言語のNULL参照を「必ずアドレス0へアクセスすること」と理解している方
Cの未定義動作と、Unix/Linuxで見られるSIGSEGVを区別したい方
JavaのNullPointerExceptionがどこまで仕様で保証されるのか知りたい方
コンパイラー、CPU、OS、JVMの役割を層に分けて整理したい方
本記事で得られること
数値0、アドレス0、CのNULL、Javaのnullを区別できます。
Cの未定義動作と、典型的な実行環境での停止過程を分けて説明できます。
Javaのexplicit null checkとimplicit null checkを、実装方法として位置づけられます。
CとJavaの決定的な違いを、言語仕様上の契約として説明できます。
第1章 NULLと0番地は同じなのか
最初に分けるのは、見た目が同じ「0」に見える4つの層です。CPUにとっての整数0と、命令がアドレスとして使う仮想アドレス0は同じビット列になり得ますが、そこでCやJavaの言語上の意味まで同じになるわけではありません。

図1 「0」に見える値を、整数、アドレス、Cのnull pointer、Javaのnullという4つの層に分けます。
図の見方
数値0は、CPUが整数演算で扱う値です。
仮想アドレス0は、ロードやストア命令がアドレスとして使う位置です。ページテーブルの設定次第では、ハードウェア上アクセス可能な構成も作れます。
Cでは、null pointerはどのオブジェクトや関数も指していないことを表すポインター値です。伝統的なCでは整数定数式0やNULLが使われてきました。
Javaのnullは参照型に属する値で、整数0とは型システム上別物です。
C23について
C23では、従来の0やNULLに加えてnullptrとnullptr_tが導入されました。本記事の画像は従来から広く使われている0とNULLを中心に説明していますが、「null pointerは単なる整数0そのものではない」という中心点は変わりません。
第2章 CでNULL参照すると誰が「落とす」のか
Cでnull pointerを参照したとき、C言語仕様がSIGSEGVや特定の例外を要求しているわけではありません。言語上は未定義動作であり、コンパイラーには特定の結果を実現する義務がありません。

図2 Cの未定義動作と、典型的なOS環境でSIGSEGVなどへ至る実行過程を分けます。
次のコードは文法として記述できますが、*pの評価は未定義動作です。
int *p = NULL;
return *p;
コンパイラーが通常のロード命令を残し、低位アドレスが未マップになっている環境では、CPUのメモリアクセス、MMUのアドレス変換、ページフォルト、OSの例外処理という流れを経て、SIGSEGVなどとして観測されることがあります。
ここでの注意点は、「典型的にSIGSEGVになる」と「C言語仕様がSIGSEGVを保証する」は別の主張だということです。未定義動作を根拠に、コンパイラーがコードを削除したり、到達不能として扱ったり、別の命令へ変形したりする可能性もあります。
第3章 Javaのnullは何が違うのか
Javaでは、null参照に対してフィールドへアクセスしたり、インスタンスメソッドを呼び出したりすると、観測可能な結果としてNullPointerExceptionになることが言語仕様とJVM仕様に含まれています。

図3 Javaが保証する結果と、JVM・JITが選べる内部実装を分けます。
Object obj = null;
obj.toString();
Javaが保証するのは、機械語に必ずif (reference == null)という比較が入ることではありません。JVMやJITは、明示的に比較するexplicit null checkを使うことも、通常のメモリアクセスで発生したハードウェア例外を捕捉してNullPointerExceptionへ変換するimplicit null checkを使うこともできます。
implicit null checkはJava言語そのものの必須実装方式ではありません。たとえばHotSpotにはimplicit null checkを生成する設定と実装がありますが、別のJVMが異なる方法で同じJavaの意味論を実現しても構いません。
第4章 CとJavaの決定的な違い
CとJavaを比べるときは、「最終的に同じようなCPU命令を使うか」よりも、「null参照後の結果を言語が保証するか」を見ます。

図4 実装技法ではなく、言語仕様上の契約を軸にCとJavaを比較します。
4つの層で整理する
言語意味論では、Cのnull pointer参照は未定義動作、Javaのnull参照はNullPointerExceptionです。
コンパイル・最適化では、Cは未定義動作を前提にコードを変形できます。Java処理系はNullPointerExceptionという観測結果を壊せません。
実行形式では、Cは通常ネイティブコードへ、Javaは通常バイトコードとJVM・JITを経て実行されます。
CPU・MMU・OSの層では、どちらも通常のメモリアクセスやハードウェア例外を利用する実装があり得ます。
このように層を分けると、「どちらも最終的にはページフォルトを使うことがあるから同じ」という説明も、「Javaは必ずCPUへ渡す前にnullを比較する」という説明も、どちらも一般化しすぎていることが分かります。
まとめ
0は整数値としてもアドレスとしても使えますが、CのNULLやJavaのnullの言語上の意味まで同じではありません。
Cのnull pointer参照は未定義動作であり、SIGSEGVは典型的な環境での観測結果の一つです。
Javaのnull参照ではNullPointerExceptionが保証されますが、明示的比較を使うかハードウェア例外を利用するかは実装の選択です。
CとJavaの決定的な違いは、ハードウェアの使い方ではなく、言語仕様がプログラムへ約束する結果です。
出典メモ
元記事: 入力時は C:\Users\hoehoe\Downloads\記事掛け合い\C言語とJava_NULLポインター_意味論_コンパイル_実行時機構.md。記事完成後は C:\Users\hoehoe\Downloads\記事掛け合い\記事作成済み\記事196_C言語とJava_NULLポインター_意味論_コンパイル_実行時機構.md に保管。
掛け合い構成の参考資料: C:\Users\hoehoe\Downloads\記事掛け合い\コイ丸先輩 × シャケもん 掛け合いインフォグラフィック生成指示【独立1〜4枚生成・厳格版】.md
使用画像: 同フォルダーの掛け合いインフォグラフィック4枚。公開記事用コピーは 02記事ソース\記事196_C言語とJavaのNULLとnull_画像 に保管。
C言語の確認資料: ISO/IEC JTC1/SC22/WG14, N1570(C11公開ドラフト)およびN3039(C23のnullptr提案文書)。
Javaの確認資料: Oracle, Java Language Specification Chapter 15、およびJava Virtual Machine Specificationのgetfield等の実行時例外規定。
JVM実装の確認資料: OpenJDK HotSpotソースのImplicitNullChecks設定。
確認日: 2026-08-24
参考リンク
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3039.htm
https://docs.oracle.com/javase/specs/jls/se24/html/jls-15.html
https://docs.oracle.com/javase/specs/jvms/se25/jvms25.pdf
https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/globals.hpp
