ヌルポインタ

コンピューティングにおいてヌル ポインターまたはヌル参照は、ポインターまたは参照が有効なオブジェクトを参照していないことを示すために保存される値です。プログラムでは、長さが不明なリストの終了や、何らかのアクションの実行の失敗などの状況を表すためにヌル ポインターが日常的に使用されます。このヌル ポインターの使用は、 null 許容型オプション型Nothing値に例えることができます

ヌル ポインターを初期化されていないポインターと混同しないでください。ヌル ポインターは、有効なオブジェクトを指すどのポインターとも等しくないことが保証されています。ただし、一般に、ほとんどの言語ではこのような保証はありません。他の有効なポインターと比較すると等しくなる場合もあれば、ヌル ポインターと比較すると等しくなる場合もあります。両方が異なるタイミングで発生する場合や、比較が未定義の動作になる場合もあります。また、このようなサポートを提供する言語では、正しい使用方法は各開発者の個々の経験とリンター ツールによって異なります。適切に使用された場合でも、ヌル ポインターは意味的に不完全です。これは、「該当なし」値と「不明」値、または「将来」値の違いを表現できないためです。

ヌルポインタは意味のあるオブジェクトを指していないため、その(無効な)メモリ位置に格納されているデータにアクセスしようとすると、実行時エラーが発生したり、プログラムがすぐにクラッシュしたりする可能性があります。これがヌルポインタエラーです。これは最も一般的なタイプのソフトウェアの脆弱性の1つであり、[ 1]この概念を導入したトニー・ホーアはこれを「10億ドルの間違い」と呼んでいます。

Cでは、どんな型の 2 つのヌルポインタも等しいことが保証されています。[2]プリプロセッサマクロはNULL、 で実装定義のヌルポインタ定数として定義されています[3]これは、 C99では、 型に変換された整数値として移植可能に表現できます( void 型へのポインタを参照)。[4] C 標準では、ヌルポインタがメモリアドレス 0 へのポインタと同じであるとは述べられていませんが、実際にはそうである可能性があります。Cでは、ヌルポインタの逆参照は未定義の動作であり、[5]準拠の実装では、逆参照されるポインタがヌルではないと想定できます。 <stdlib.h>((void *)0)0 void*

実際には、ヌル ポインターを逆参照すると、マップされていないメモリからの読み取りまたは書き込みが試行され、セグメンテーション違反またはメモリ アクセス違反が発生する可能性があります。これは、プログラム クラッシュとして現れるか、プログラム コードでキャッチできるソフトウェア例外に変換される可能性があります。ただし、そうではない状況もあります。たとえば、x86 リアル モードでは、アドレス0000:0000は読み取り可能で、通常は書き込みも可能なため、そのアドレスへのポインターを逆参照することは完全に有効ですが、通常は望ましくないアクションであり、アプリケーションで未定義ではあるがクラッシュしない動作につながる可能性があります。アドレス 0 へのポインターの逆参照が意図的で明確に定義されている場合もありますたとえば、16 ビット リアル モード x86 デバイス用に C で記述されたBIOSコードは、書き込み用にヌル ポインターを逆参照することで、マシンの物理アドレス 0 に割り込み記述子テーブル(IDT) を書き込む場合があります。また、コンパイラーがヌル ポインターの逆参照を最適化して削除し、セグメンテーション違反を回避しながら、他の望ましくない動作を引き起こす可能性もあります。[6]

C++

C++では、NULLマクロはCから継承されていますが、ゼロの整数リテラルは伝統的にヌルポインター定数を表すために好まれてきました。[7]しかし、C++11では代わりに明示的なヌルポインター定数nullptrと型が導入されましたnullptr_t

他の言語

一部のプログラミング言語環境(たとえば、少なくとも 1 つの独自の Lisp 実装)では、[要引用]ヌル ポインターとして使用される値 ( Lispnilでは と呼ばれる) は、実際には実装に役立つ内部データ ブロックへのポインターである可能性があります (ただし、ユーザー プログラムからは明示的にアクセスできません)。そのため、同じレジスタを便利な定数として使用し、実装内部にすばやくアクセスすることができます。これはベクトルと呼ばれますnil

タグ付きアーキテクチャを持つ言語では、おそらく null のポインターを、例外的なケースの明示的な処理を強制するタグ付きユニオンに置き換えることができます。実際、おそらく null のポインターは、計算されたタグを持つ タグ付きポインターと見なすことができます。

プログラミング言語は、ヌルポインターに異なるリテラルを使用します。たとえば、Python では、ヌル値は と呼ばれます。PascalNoneと Swift ではヌルポインターは と呼ばれます。Eiffel では、参照と呼ばれますnilvoid

ヌル参照

ヌルポインタは意味のあるオブジェクトを指していないため、ヌルポインタを逆参照(つまり、そのメモリ位置に格納されているデータにアクセス)しようとすると、通常は(必ずではありませんが)実行時エラーが発生したり、プログラムが即座にクラッシュしたりします。MITRE、ヌルポインタエラーを最も一般的に悪用されるソフトウェアの脆弱性の1つとして挙げています。[8]

  • Cでは、ヌルポインタの逆参照は未定義の動作です。[5]多くの実装では、ヌルポインタ表現が、オブジェクトを格納するためにシステムによって割り当てられることのないアドレスとして選択されるため、このようなコードによってプログラムがアクセス違反で停止します。ただし、この動作は普遍的ではありません。また、コンパイラは未定義の動作がないという前提でプログラムを最適化することが許可されているため、保証されていません。
  • Delphiおよび他の多くの Pascal 実装では、定数はメモリの最初のアドレスへの NULL ポインターを表し、マネージ変数の初期化にも使用されます。これを逆参照すると、ユニットが句にリンクされている場合は、Pascal 例外インスタンスnilにマップされる外部 OS 例外が発生しますEAccessViolationSystem.SysUtilsuses
  • Javaでは、null 参照にアクセスするとNullPointerException(NPE) が発生しますが、これはエラー処理コードによってキャッチできますが、このような例外が発生しないようにすることが推奨されます。
  • Lispでは、はファーストクラス オブジェクトnilです。慣例により、は と同様に ですしたがって、これらのコンテキストで逆参照してもエラーは発生しませんが、不適切に記述されたコードは無限ループに陥る可能性があります。(first nil)nil(rest nil)nil
  • .NETでは、null 参照にアクセスすると がNullReferenceExceptionスローされます。これらをキャッチすることは一般的に悪い習慣と考えられていますが、この例外タイプはプログラムによってキャッチされ、処理される可能性があります。
  • Objective-Cでは、nilプログラムを中断させることなく、メッセージをオブジェクト(ヌルポインタ)に送信することができます。メッセージは単に無視され、戻り値(ある場合)は型に応じてnilまたは になります。 [9]0
  • スーパーバイザモードアクセス防止(SMAP)が導入される前は、ページゼロを攻撃者のアドレス空間にマッピングし、その結果、ヌルポインタがその領域を指すようにすることで、ヌルポインタ参照バグを悪用される可能性がありました。これにより、場合によってはコードが実行される可能性があります。 [10]

緩和

ヌルポインタ参照のデバッグを容易にする技術があります。[11] Bondら[11]は、ヌル伝播を追跡するためにJava仮想マシン(JVM)を変更することを提案しています。

純粋関数型言語や、多くのインタープリタ言語や仮想マシン言語で実行されるユーザー コードでは、ポインターへの直接アクセスが提供されず、純粋関数型言語の場合はすべてのコードとデータが不変であるため、ヌル ポインターの逆参照の問題は発生しません。

言語がポインタを提供または利用し、ポインタがそうでなければ無効になる可能性がある場合、静的解析やその他の技術を介してコンパイル時のチェックを提供することで、実行時のnull参照を軽減または回避できる可能性があります。これは、 Eiffelプログラミング言語の最新バージョン、[12]、D[13]Rust [14]に見られるような言語機能からの構文支援に向けた動きが急速に広まっているためです

一部の言語では、外部ツールを使用して同様の分析を実行できます。


ヌルポインタの代替

経験則として、構造体またはクラスの各タイプに対して、ビジネス ロジックの状態を表すオブジェクトをいくつか定義し、null の未定義の動作を置き換えます。たとえば、構造体内のフィールドが現在は使用できないことを示すには「future」を使用します (ただし、将来定義されることが事前にわかっています)。また、正規化されていない構造体内のフィールドを示すには「not applicable」を使用します。フィールドを初期化できなかったことを示すには「error」を使用します。また、完全なプログラム、スレッド、要求、またはコマンドの通常の実行が停止する可能性のある「timeout」を使用します。

歴史

2009年、トニー・ホーアは[15]、 1965年にALGOL W言語 の一部としてヌル参照を発明したと述べています。2009年のその文献で、ホーアは彼の発明を「10億ドルの間違い」と表現しています。

私はこれを 10 億ドルの失敗と呼んでいます。それは 1965 年のヌル参照の発明でした。当時、私はオブジェクト指向言語 (ALGOL W) における参照用の最初の包括的な型システムを設計していました。私の目標は、コンパイラによって自動的にチェックが実行され、参照の使用がすべて絶対に安全であることを保証することでした。しかし、実装が非常に簡単であるという理由だけで、ヌル参照を入れたいという誘惑に抗うことができませんでした。これにより、無数のエラー、脆弱性、システム クラッシュが発生し、過去 40 年間でおそらく 10 億ドルの苦痛と損害が発生しました。

参照

ノート

  1. ^ 「CWE-476: NULL ポインタ参照」。MITRE
  2. ^ ISO/IEC 9899、条項6.3.2.3、段落4。
  3. ^ ISO/IEC 9899、条項 7.17、段落 3: NULL... これは実装定義の null ポインター定数に展開されます...
  4. ^ ISO/IEC 9899、6.3.2.3項、第3段落。
  5. ^ ISO/IEC 9899、条項6.5.3.2、段落4、特に脚注87。
  6. ^ Lattner, Chris (2011-05-13). 「未定義の動作についてすべての C プログラマが知っておくべきこと #1/3」。blog.llvm.org 。2023-06-14にオリジナルからアーカイブ。2023-06-14に取得
  7. ^ Stroustrup, Bjarne (2001 年 3 月)。「第 5 章:修飾子(§5.4) は、 の偶発的な再定義を防ぎ定数が必要な場所で が確実に使用されるようにします。」 C++プログラミング言語(第 3 版の第 14 刷)。米国およびカナダ: Addison–Wesley。p. 88。ISBN
    constNULLNULL 0-201-88954-4
  8. ^ 「CWE-476: NULL ポインタ参照」。MITRE
  9. ^ Objective-C 2.0 プログラミング言語、「nil へのメッセージの送信」セクション。
  10. ^ 「OS X の AppleGraphicsDeviceControl におけるカーネル NULL ポインタ参照の脆弱性」
  11. ^ ab Bond, Michael D.; Nethercote, Nicholas; Kent, Stephen W.; Guyer, Samuel Z.; McKinley, Kathryn S. (2007). 「Tracking bad apples」.第 22 回 ACM SIGPLAN 会議の議事録 - オブジェクト指向プログラミング システムとアプリケーション - OOPSLA '07 . p. 405. doi :10.1145/1297027.1297057. ISBN 9781595937865. S2CID  2832749。
  12. ^ 「ボイドセーフティ:背景、定義、ツール」 。 2021年11月24日閲覧
  13. ^ Bartosz Milewski. 「SafeD – Dプログラミング言語」 。 2014年7月17日閲覧
  14. ^ “Fearless Security: Memory Safety”. 2020年11月8日時点のオリジナルよりアーカイブ。 2020年11月4日閲覧
  15. ^ Tony Hoare (2009-08-25). 「Null 参照: 10 億ドルの損失」. InfoQ.com.

参考文献

  • 合同技術委員会 ISO/IEC JTC 1、小委員会 SC 22、ワーキンググループ WG 14 (2007-09-08)。国際規格 ISO/IEC 9899 (PDF) (委員会草案)。{{cite book}}: CS1 maint: 複数名: 著者リスト (リンク) CS1 maint: 数値名: 著者リスト (リンク)
Retrieved from "https://en.wikipedia.org/w/index.php?title=Null_pointer&oldid=1227139784#C++"