ヌルポインターにアドレスゼロが使用されるのはなぜですか?
C(またはC++)では、値がゼロのポインターは特別です。メモリーを解放した後、ポインターをゼロに設定することをお勧めします。これは、ポインターを再度解放することは危険ではないためです。 mallocを呼び出すと、メモリを取得できない場合は、値がゼロのポインターを返します。 if (p != 0)
を常に使用して、渡されたポインターが有効であることを確認します。
しかし、メモリのアドレス指定は0から始まるため、0は他のアドレスと同じように有効なアドレスではありませんか?その場合、nullポインターの処理に0をどのように使用できますか?負の数が代わりにnullではないのはなぜですか?
編集:
たくさんの良い答え。私の心がそれを解釈するので表された答えで言われたことを要約します、そして、私が誤解するならば、コミュニティが私を修正することを望みます。
プログラミングの他のすべてと同様に、それは抽象化です。単なる定数であり、実際にはアドレス0とは関係ありません。C++ 0xは、キーワード
nullptr
を追加することでこれを強調しています。それはアドレスの抽象化でさえありません。C標準で指定された定数であり、コンパイラは「実際の」アドレスと決して等しくならず、0でない場合は他のNULLポインターと等しいことを確認する限り、他の数値に変換できますプラットフォームに使用する最適な値。
抽象化ではない場合(初期の場合)、アドレス0はシステムによって使用され、プログラマーの立ち入りが禁止されています。
私の負の数の提案は、少しワイルドなブレインストーミングでした、と私は認めます。アドレスに符号付き整数を使用することは、ヌルポインター(-1など)を除いて、有効なアドレスを作成する正の整数と無駄になった負の数の間で値空間が均等に分割されることを意味します。
数値が常にデータ型で表現できる場合、それは0です(おそらく1もです。符号なしの場合は0または1、符号付きの場合は符号付きビット、または2ビットの整数[-2、1]になります。ただし、0がnullで、1がメモリ内の唯一のアクセス可能なバイトになるようにできます。)
それでも私の心には未解決の何かがあります。 Stack Overflowの質問特定の固定アドレスへのポインターは、nullポインターの0が抽象化である場合でも、他のポインター値は必ずしも必要ではありません。これにより、別のStack Overflow質問アドレス0にアクセスしたいですか?を投稿することになります。
2ポイント:
ソースコードの定数値0のみがNULLポインターです。コンパイラの実装は、実行中のコードで必要な値または必要な値を使用できます。一部のプラットフォームには、実装がヌルポインターとして使用する「無効な」特別なポインター値があります。 C FAQには質問があります "実際に、実際のマシンは非ゼロのNULLポインターを使用していますか、または異なる型へのポインターの異なる表現を使用していますか?" C++標準では、「値がゼロの整数定数式を変換すると常にNULLポインターが生成されますが、他の式は変換される」という明確な注記があります。値がゼロであっても、nullポインタを生成する必要はありません」。
プラットフォームは、負の値をアドレスと同じように使用できます。C標準では、nullポインターを示すために使用するものを選択するだけで、ゼロが選択されました。他のセンチネル値が考慮されたかどうかは正直わかりません。
NULLポインターの唯一の要件は次のとおりです。
- 実際のオブジェクトへのポインタと等しくないことを比較することが保証されています
- 任意の2つのNULLポインターは等しいと比較されます(C++はこれを改良して、同じ型へのポインターのみを保持する必要があるようにします)
歴史的に、0から始まるアドレススペースは常にROMであり、一部のオペレーティングシステムまたは低レベルの割り込み処理ルーチンに使用されていました。特にアドレス0には何も割り当てないでください。
IIRC、「ヌルポインター」値はゼロであるとは限りません。コンパイラは、0をシステムに適切な「null」値に変換します(実際には、おそらく常にゼロですが、必ずしもそうではありません)。ポインタをゼロと比較するときは常に同じ変換が適用されます。ポインターは相互に比較することも、この特殊値0に対してのみ比較することもできるため、プログラマーはシステムのメモリー表現について何も知らないようになります。彼らが42などの代わりに0を選んだ理由については、ほとんどのプログラマが0でカウントを開始するためだと推測します:)(また、ほとんどのシステムでは0が最初のメモリアドレスであり、私が説明しているような翻訳を実際に行うことはめったにありません;言語はそれらを許可しています)。
ポインタコンテキストの定数ゼロの意味を誤解している必要があります。
CでもC++ポインターでも、「値がゼロ」になることはありません。ポインターは算術オブジェクトではありません。それらは、「ゼロ」または「負」またはその性質のもののような数値を持ちます。したがって、「ポインタ...値がゼロ」についての文は、まったく意味がありません。
CおよびC++では、ポインターは予約済みnull-pointer valueを持つことができます。 NULLポインター値の実際の表現は、「ゼロ」とは関係ありません。特定のプラットフォームに適切なものであれば何でもかまいません。ほとんどのプラットフォームでは、nullポインター値が実際のゼロアドレス値によって物理的に表されることは事実です。ただし、一部のプラットフォームでアドレス0が実際に何らかの目的で使用されている場合(つまり、アドレス0でオブジェクトを作成する必要がある場合)、そのようなプラットフォームのNULLポインター値は異なる可能性があります。たとえば、0xFFFFFFFF
アドレス値または0xBAADBAAD
アドレス値として物理的に表すことができます。
それにもかかわらず、特定のプラットフォームでヌルポインター値がどのように表現されるかに関係なく、コードでは引き続き定数0
によってヌルポインターを指定します。 NULLポインター値を特定のポインターに割り当てるには、p = 0
などの式を引き続き使用します。あなたが望むものを実現し、それを適切なヌルポインタ値表現に変換する、つまり、0xFFFFFFFF
のアドレス値をポインタp
に入れるコードに変換するのは、コンパイラの責任です。例えば。
つまり、ソースコードで0
を使用してNULLポインター値を生成するという事実は、NULLポインター値が何らかの方法でアドレス0
に関連付けられていることを意味しません。ソースコードで使用する0
は、nullポインタ値が指す「実際の物理アドレス」とはまったく関係のない「構文糖」です。
しかし、メモリアドレス指定は0から始まるので、0は他のアドレスと同じように有効なアドレスではありませんか?
一部/多く/すべてのオペレーティングシステムでは、メモリアドレス0は何らかの方法で特別です。たとえば、無効または存在しないメモリにマップされることが多く、アクセスしようとすると例外が発生します。
負の数が代わりにヌルではないのはなぜですか?
ポインタ値は通常、符号なしの数値として扱われると思います。それ以外の場合、たとえば32ビットポインタは、4 GBではなく2 GBのメモリしかアドレスできません。
私の推測では、より少ない命令でテストできるので、無効なポインターを定義するためにマジック値0が選択されたでしょう。一部のマシン言語では、レジスタをロードするときにデータに応じてゼロおよび符号フラグが自動的に設定されるため、単純なload then命令とnullポインタをテストし、個別の比較命令を実行せずに分岐命令を実行できます。
(ほとんどのISAは、ロードではなくALU命令にのみフラグを設定します。通常、C sourceを解析するコンパイラを除き、計算によってポインタを生成することはありません。しかし、少なくともあなたは必要ありません。比較する任意のポインタ幅定数。)
最初に作業したマシンであるCommodore Pet、Vic20、およびC64では、RAMはロケーション0で開始されたので、本当に必要な場合はnullポインターを使用して読み書きできます。 。
単なる慣習だと思います。無効なポインターをマークするには、何らかの値が必要です。
1バイトのアドレススペースを失うだけで、問題になることはめったにありません。
負のポインターはありません。ポインターは常に符号なしです。また、それらが否定的である可能性がある場合、慣例により、アドレス空間の半分が失われます。
CはNULLポインターを表すために0を使用しますが、ポインター自体の値がゼロでない場合があることに注意してください。ただし、ほとんどのプログラマーは、ヌルポインターが実際には0であるシステムのみを使用します。
しかし、なぜゼロなのでしょうか?まあ、それはすべてのシステムが共有する1つのアドレスです。また、多くの場合、下位アドレスはオペレーティングシステム用に予約されているため、値はアプリケーションプログラムの立ち入り禁止として機能します。ポインタへの偶発的な整数値の割り当ては、他のものと同様にゼロになる可能性があります。
削除後にポインターをnullに設定しないことについての議論に関しては、将来は「エラーを公開」を削除します...
あなたが本当にこれを本当に心配しているなら、より良いアプローチ、機能することが保証されているものは、assert()を活用することです:
...
assert(ptr && "You're deleting this pointer twice, look for a bug?");
delete ptr;
ptr = 0;
...
これには、いくつかの追加の入力と、デバッグビルド中の1つの追加のチェックが必要ですが、必要なものを確実に提供できます。ptrが「2回」削除されたときに注意してください。コメントディスカッションで示されている代替手段は、ポインターをnullに設定しないためクラッシュする可能性がありますが、単に成功するとは限りません。さらに悪いことに、上記とは異なり、これらの「バグ」のいずれかがシェルフに到達すると、ユーザーにクラッシュ(またはさらに悪いことに!)を引き起こす可能性があります。最後に、このバージョンでは、プログラムを引き続き実行して、実際に何が起こるかを確認できます。
私はこれが尋ねられた質問に答えないことを理解していますが、コメントを読んでいる人がfree()または2回削除します。可能な少数のケースでは、デバッグツールとして未定義の動作を使用することは決して良い方法ではありません。無効なポインターを削除することによって最終的に引き起こされるバグを見つけ出す必要のある人は誰もいません。これらの種類のエラーは数時間かけて追い詰められ、ほぼ完全に予想外の形でプログラムに影響を及ぼしますが、元の問題にさかのぼることは不可能です。
従来、アプリケーションのメモリ不足はシステムリソースによって占められていました。当時、ゼロがデフォルトのヌル値になりました。
これは必ずしも最新のシステムには当てはまりませんが、ポインター値を設定するメモリ割り当て以外の値を設定することは、依然として悪い考えです。
多くのオペレーティングシステムがヌルポインター表現に全ビットゼロを使用する重要な理由は、これはmemset(struct_with_pointers, 0, sizeof struct_with_pointers)
などがstruct_with_pointers
内のすべてのポインターをヌルポインターに設定することを意味することです。これはC標準では保証されていませんが、多くのプログラムがそれを想定しています。
何らかの価値が必要です。明らかに、ユーザーが正当に使用する可能性のある値を踏むことは望ましくありません。 Cランタイムはゼロで初期化されたデータのBSSセグメントを提供するため、ゼロを初期化されていないポインター値として解釈することはある程度意味があると推測します。
センチネル値の選択は任意であり、これは実際には次のバージョンのC++(非公式には「C++ 0x」として知られ、将来的にはISO C++ 2011として知られる可能性が高い) null値ポインタを表すキーワードnullptr
。 C++では、PODおよびデフォルトコンストラクターを持つオブジェクトの初期化式として値0を使用できます。これは、ポインターの初期化の場合にセンチネル値を割り当てるという特別な意味があります。負の値が選択されなかった理由については、アドレスは通常0から2の範囲ですNある値Nに対して-1。つまり、アドレスは通常、符号なしの値として扱われます。最大値がセンチネル値として使用された場合、メモリのサイズに応じてシステムごとに異なる必要がありますが、0は常に表現可能なアドレスです。また、メモリアドレス0は通常プログラムでは使用できず、現在ほとんどのOSにはメモリの下位ページにカーネルの一部がロードされており、そのようなページは通常、プログラム(カーネルを保存)によってタッチ(参照解除)すると、障害が発生します。
古いDECマシンの1つ(PDP-8、私が思う)では、Cランタイムはメモリの最初のページをメモリ保護するため、そのブロック内のメモリにアクセスしようとすると例外が発生します。
このスレッドにはすでに多くの良い答えがあります。 nullポインターに値0
を好む理由はおそらく多くありますが、さらに2つ追加します。
- C++では、ポインターをゼロで初期化すると、ポインターがnullに設定されます。
- 多くのプロセッサでは、値を0に設定したり、他の定数よりも0に等しい/等しくないことをテストする方が効率的です。
値 0
は、特定の式でさまざまな意味を持つ特別な値です。何度も指摘されているように、ポインターの場合は、おそらく「デフォルトのセンチネル値をここに挿入する」という最も便利な方法だったために使用されます。定数式として、ポインター式のコンテキストでは、ビット単位のゼロ(つまり、すべてのビットがゼロに設定される)と同じ意味を持ちません。 C++には、ポインターメンバーやメンバー関数へのポインターなど、NULL
のビット単位のゼロ表現を持たないいくつかの型があります。
ありがたいことに、C++ 0xには、「積分式のビット単位ゼロにもマップされない既知の無効なポインターを意味する式」の新しいキーワードnullptr
があります。 C++をターゲットにできるシステムがいくつかありますが、これらのシステムではバーフなしでアドレス0の逆参照が可能です。
これは、C/C++でのポインターの実装に依存しています。 NULLがポインターへの割り当てで同等である特定の理由はありません。
OSがアドレス0に書き込むことを許可することはほとんどありません。OS固有のものを低メモリに固定することは一般的です。つまり、IDT、ページテーブルなど(テーブルはRAM内にある必要があり、テーブルの一番下の部分を固定するほうが、RAM is。 OSを気にせずに、システムテーブルを自由に編集することはできません。
これは、C&RがCを作成したときにK&Rの頭の中になかったかもしれませんが、(0 == nullは覚えやすいという事実とともに)0が人気のある選択肢になります。