nullポインタにアドレス0が使用されるのはなぜですか?


121

C(またはそのことについてはC ++)では、値がゼロの場合、ポインターは特別です。メモリーを解放した後、ポインターをゼロに設定することをお勧めします。mallocを呼び出すと、メモリを取得できない場合は値0のポインタが返されます。私はif (p != 0)常に、渡されたポインタが有効であることなどを確認するために使用しています。

しかし、メモリアドレス指定は0から始まるので、0は他のアドレスと同じように有効なアドレスではありませんか?その場合、nullポインターの処理に0をどのように使用できますか?代わりに負の数がnullにならないのはなぜですか?


編集:

良い答えがたくさん。私の心がそれを解釈しているように表現された答えで言われたことを要約し、私が誤解した場合、コミュニティが私を修正してくれることを願っています。

  • プログラミングの他のすべてのように、それは抽象化です。単なる定数であり、実際にはアドレス0に関連していません。C++ 0xは、キーワードを追加することでこれを強調していnullptrます。

  • これはアドレスの抽象化でもありません。C標準で指定されている定数であり、コンパイラが「実際の」アドレスと決して等しくないことを確認する限り、他の数値に変換できます。0がプラットフォームに使用するのに最適な値。

  • 初期の場合のように抽象化されていない場合、アドレス0はシステムによって使用され、プログラマーの立ち入りを制限します。

  • 私の負の数の提案は少しワイルドなブレーンストーミングだったと私は認めます。アドレスに符号付き整数を使用することは、ヌルポインター(-1など)を除いて、値空間が有効なアドレスを作る正の整数と無駄になる負の数に均等に分割されることを意味する場合、少し無駄です。

  • いずれかの数値が常にデータ型で表現できる場合、それは0です(おそらく1もそうです。符号なしの場合は0または1になる1ビット整数、または符号付きの場合は単に符号付きビット、または[-2、1]になります。ただし、0がnullで、1がメモリ内の唯一のアクセス可能なバイトであることを確認できます。)

それでも未だに未解決な部分があります。スタックオーバーフローの質問特定の固定アドレスへのポインターは、nullポインターの0が抽象化であっても、他のポインター値は必ずしもそうではないことを示しています。これにより、別のスタックオーバーフローの質問を投稿するようになりました。アドレス0にアクセスできますか?


11
CやC ++で一般的なイディオムを簡単に変更if (p != 0)するif (p)こともできますが、Javaを使用する場合は習慣から抜け出す必要があります。
ポールトンブリン

14
何かを2回削除すると、コードが間違っていることを意味します。後からポインターをnullに設定しないことをお勧めします。そうすることで、問題を抑制せずにクラッシュして修正できるようになります。いずれにせよ、アドレスが何らかの整数であると想定するのは間違いです。これは必ずしも正しいとは限りません。0 は、実装固有の実際のポインタ値を表すだけです。「負のアドレス」は、概念的には意味がありません。
GManNickG

6
@GMan:多分、のようなクラッシュを強制するアドレスにポインタを設定するのも良い考え0xDEADBEEFです。
Billy ONeal、

5
死ぬことのない質問!

8
@Noah:ポイントはnullに設定されます->プログラミングエラーを非表示にします。nullに設定しないでください->プログラミングエラーを検索します。あなたのことは知りませんが、コードを修正してもらいたいのですが。
GManNickG

回答:


65

2点:

  • ソースコードの定数値0のみがnullポインターです。コンパイラーの実装では、実行中のコードで必要な値または必要な値を使用できます。一部のプラットフォームには、実装がnullポインターとして使用する可能性がある「無効」な特別なポインター値があります。CのFAQには、「まじめな話ですが、実際のマシンでは実際にゼロ以外のnullポインターを使用しているのですか、それとも異なる型へのポインターの異なる表現を使用しているのですか?、それはこのプロパティ0を使用したいくつかのプラットフォームを指し、0は実行時に異なる表現でCソースのnullポインターを示します。C ++標準には、「値がゼロの整数定数式を変換すると常にnullポインターが生成されること、

  • 負の値は、アドレスと同じようにプラットフォームで使用できます。C標準では、nullポインターを示すために使用するものを選択するだけで、ゼロが選択されました。他のセンチネル値が考慮されたかどうかは正直わかりません。

NULLポインターの唯一の要件は次のとおりです。

  • 実際のオブジェクトへのポインタと等しくないことが保証されます
  • 任意の2つのnullポインターは等しいと比較します(C ++はこれを調整して、同じ型へのポインターに対してのみ保持する必要があるようにします)

12
+1私は0が単に歴史的な理由で選ばれたと思います。(0はほとんどの場合、開始および無効なアドレスです。)もちろん、一般に、このような仮定は常に真であるとは限りませんが、0はかなりうまく機能します。
GManNickG

8
スペースも要因になっている可能性があります。Cが最初に開発された当時、メモリは今よりもはるかに高価でした。数値ゼロは、XOR命令を使用して、または即値をロードする必要なく、便利に計算できます。アーキテクチャによっては、これによりスペースを節約できる可能性があります。
Sparky

6
@GMan-あなたは正しいです。初期のCPUでは、メモリアドレス0は特別であり、実行中のソフトウェアからのアクセスに対するハードウェア保護がありました(場合によってはリセットベクタの開始であり、これを変更するとCPUがリセットまたは起動できなくなる可能性がありました)。プログラマーはこのハードウェア保護をソフトウェアのエラー検出の形式として使用し、CPUのアドレスデコードロジックで、初期化されていないポインターや無効なポインターをチェックし、CPU命令を費やす必要がなくなりました。アドレス0の目的が変わったとしても、慣例は今日まで残っています。
bta

10
Minix 16ビットコンパイラはNULLに0xFFFFを使用していました。
ジョシュア

3
多くの組み込みシステムでは、0が有効なアドレスです。値-1(すべてビット1)も有効なアドレスです。データがアドレス0で始まる場合、ROMのチェックサムを計算するのは困難です。:-(
Thomas Matthews

31

歴史的に、0から始まるアドレススペースは常にROMであり、一部のオペレーティングシステムまたは低レベルの割り込み処理ルーチンに使用されていました。現在、すべてが仮想(アドレススペースを含む)であるため、オペレーティングシステムは任意の割り当てを任意のアドレスにマップできるため、特にアドレス0には何も割り当てません。


6
それはほとんどそれです。これは歴史的な慣例によるものであり、最初のアドレスは割り込みハンドラに使用されていたため、通常のプログラムでは使用できません。また、0は「空」であり、値なし/ポインターなしとして解釈できます。
TomTom

15

IIRC、「nullポインタ」の値がゼロであるとは限りません。コンパイラーは、0をシステムに適した「null」値に変換します(実際には、常にゼロである可能性がありますが、必ずしもそうである必要はありません)。ポインターをゼロと比較するときは常に同じ変換が適用されます。ポインタを相互に比較したり、この特別な値0と比較したりすることしかできないため、プログラマはシステムのメモリ表現について何も知る必要がありません。彼らが42などの代わりに0を選択した理由については、ほとんどのプログラマーが0からカウントを開始するためだと思います:)私が説明しているような実際の翻訳はめったに実際には行われません;言語はそれらを可能にします)。


5
@ジャスティン:あなたは誤解しました。定数0は常に nullポインターです。@meadorが言っていることは、ヌルポインター(定数0で示される)がアドレス0に対応していない可能性があるということです。一部のプラットフォームでは、nullポインター(int* p = 0)を作成すると、値0xdeadbeefまたはそれが好むその他の値を含むポインターが作成される場合があります。0はnullポインターですが、nullポインターは必ずしもアドレス0へのポインターではありません。:)
jalf

NULLポインターは予約済みの値であり、コンパイラーに応じて任意のビットパターンになる可能性があります。NULLポインタが、それはアドレス0を指していることを意味するものではありません
Sharjeelアジズ

3
しかし、@ Jalf、定数0 常にnullポインタであるとは限りません。これは、コンパイラーにプラットフォームの実際の nullポインターを入力させるときに作成するものです。事実上、ヌル・ポインタは通常ありませんが、アドレスゼロに対応して、そして私はそれがある理由を尋ねるとジョエルの質問を解釈します。結局、そのアドレスにメモリの有効なバイトがあると思われるので、有効なバイトをプレイから削除する代わりに、存在しないバイトの存在しないアドレスを使用しないのはなぜですか?(私がジョエルが考えていたと想像していることを書いているのであり、私が自分に問いかけている質問ではありません。)
Rob Kennedy

@ロブ:ちょっと。私はあなたが何を意味するかを知っています、そしてあなたは正しいですが、私もそうです。:)定数整数0は、ソースコードレベルでのnullポインターを表します。nullポインタを0と比較すると、trueが生成されます。ポインターに0を割り当てると、そのポインターはnullに設定されます。0 NULLポインターです。ただし、ヌルポインターの実際のメモリ内表現は、ゼロビットパターンとは異なる場合があります。(とにかく、私のコメントは
@Joel

@jalf @Rob明確にするためにいくつかの用語が必要だと思います。:)§4.10/ 1から:「nullポインター定数は、整数型のゼロに評価される整数定数式の右辺値です。null ポインター定数はポインター型に変換できます;結果はその型のnullポインター値であり、オブジェクトへのポインターまたは関数タイプへのポインターの他のすべての値と区別できます。」
GManNickG

15

ポインタコンテキストでの定数ゼロの意味を誤解している必要があります。

CでもC ++ポインターでも、「値がゼロ」になることはありません。ポインタは算術オブジェクトではありません。「ゼロ」や「負」などの数値は使用できません。したがって、「ポインタ...値が0である」というあなたの声明は、単に意味をなさない。

CおよびC ++では、ポインターは予約済みのNULLポインター値を持つことができます。ヌルポインター値の実際の表現は、「ゼロ」とは何の関係もありません。特定のプラットフォームに適切なものであれば何でもかまいません。ほとんどのプラットフォームで、ヌルポインター値が実際のゼロアドレス値によって物理的に表されることは事実です。ただし、一部のプラットフォームでアドレス0が実際に何らかの目的で使用されている場合(つまり、アドレス0でオブジェクトを作成する必要がある場合)、そのようなプラットフォームでのnullポインター値はおそらく異なるでしょう。たとえば、物理的に0xFFFFFFFFアドレス値またはアドレス値として表すことができます0xBAADBAAD

それでも、特定のプラットフォームでnullポインター値がどのように表現されるかに関係なく、コードでは引き続きnullポインターをconstantで指定します0。nullポインター値を特定のポインターに割り当てるには、のような式を引き続き使用しますp = 0。必要なものを認識して適切なnullポインター値表現に変換する、つまり、たとえば、のアドレス値を0xFFFFFFFFポインターpに入れるコードに変換するのはコンパイラーの責任です。

つまり、0ソースコードでnullポインター値を生成するために使用しても、nullポインター値が何らかの方法でaddressに関連付けられているわけではありません00あなたがソースコード内で使用していることは、ヌル・ポインタ値が「ポインティング」される実際の物理アドレスには全く関係がないだけで、「シンタックスシュガー」です。


3
<quote>ポインターは算術オブジェクトではありません</ quote>ポインター算術は、CおよびC ++で非常によく定義されています。要件の一部は、両方のポインターが同じコンポジット内を指すことです。nullポインターはコンポジットをポイントしないため、ポインター算術式での使用は無効です。たとえば、であるとは限りません(p1 - nullptr) - (p2 - nullptr) == (p1 - p2)
Ben Voigt

5
@Ben Voigt:言語仕様は、算術型の概念を定義しています。私が言っているのは、ポインタ型は算術型のカテゴリに属していないということです。ポインター算術は、まったく別の話であり、単なる言語の偶然です。
AnT

1
誰かがそれを知っているはずの算術オブジェクトをどのように読んでいるのかは、「算術型の意味で」であり、「算術演算子の意味で」(そのいくつかはポインターで使用可能)または「ポインター算術の意味で」ではありません。言語の一致に関する限り、算術オブジェクトには、算術型よりもポインタ算術に共通する文字が多くあります。同時に、標準はポインタ値について話します。オリジナルポスターは、おそらく意味ポインタの表現整数ではなく、ポインタ値、および明示的に0で表現する必要はありませんNULL
ベン・フォークト

たとえば、C / C ++の用語でスカラーオブジェクトという用語は、スカラータイプのオブジェクトの単なる省略形です(PODオブジェクト = PODタイプのオブジェクトと同様)。私は算術オブジェクトという用語をまったく同じ方法で使用しました。これは算術型のオブジェクトを意味します。「誰か」がそのように理解してくれることを期待しています。常に説明を求めることができない人。
AnT

1
(ハードウェアに関する限り)nullが0xffffffffで、0が完全に有効なアドレスであるシステムで作業しました
pm100

8

しかし、メモリアドレス指定は0から始まるので、0は他のアドレスと同じように有効なアドレスではありませんか?

一部/多く/すべてのオペレーティングシステムでは、メモリアドレス0は何らかの点で特別です。たとえば、無効または存在しないメモリにマップされることがよくあり、アクセスしようとすると例外が発生します。

代わりに負の数がnullにならないのはなぜですか?

ポインタ値は通常、符号なしの数値として扱われると思います。それ以外の場合、たとえば32ビットポインタは、4 GBではなく2 GBのメモリしかアドレス指定できません。


4
アドレス0が有効なアドレスであり、メモリ保護がないデバイスでコーディングしました。NULLポインターもすべてビット0でした。誤ってnullポインターに書き込んだ場合は、ゼロアドレスにあったOSの設定に爆破しました。陽気は通常起こりませんでした。
MM 2014年

1
はい:非プロテクトモードのx86 CPUでは、たとえば、アドレス0が割り込みベクターテーブルです。
ChrisW

@ChrisW:非プロテクトモードのx86では、特にアドレス0はゼロ除算割り込みベクトルであり、一部のプログラムでは完全に正当な理由で書き込みを行う場合があります。
スーパーキャット2017年

使用可能なストレージが物理アドレス0から始まるプラットフォームでも、C実装はアドレス0を使用して、アドレスが取得されないオブジェクトを保持するか、メモリの最初のワードを未使用のままにすることができます。ほとんどのプラットフォームでは、compare-with-zeroは命令を保存し、compare-with-anything-elseを保存します。そのため、ストレージの最初のワードを無駄にしても、nullにゼロ以外のアドレスを使用するよりも安価です。物事のアドレスはC標準(例えばI / Oポートまたは割り込みベクタ)でカバーされていないという要件はありませんことを注意ヌルに等しくない比較、またその...
supercat

...システムはnullポインタアクセスを他とは異なる方法で処理するため、物理位置0へのアクセスが有用で意味のあるシステムであっても、all-bits-zeroは通常「null」の適切なアドレスです。
スーパーキャット2017年

5

おそらく、より少ない命令でテストできるため、無効なポインターを定義するためにマジック値0が選択されたと思います。一部の機械語は、レジスターをロードするときにデータに従ってゼロおよび符号フラグを自動的に設定するので、個別の比較命令を実行せずに単純なロードthenおよび分岐命令でnullポインターをテストできます。

(ただし、ほとんどのISAはALU命令にフラグを設定するだけで、ロードではありません。通常、コンパイラーでC ソースを解析する場合を除いて、計算によってポインターを生成することはありません。ただし、少なくとも任意のポインター幅定数は必要ありません。と比較してください。)

私が最初に取り組んだCommodore Pet、Vic20、およびC64では、RAMは0の位置から始まったため、本当に必要な場合はnullポインターを使用して読み書きすることは完全に有効でした。


3

単なる慣習だと思います。無効なポインターをマークするには、いくつかの値が必要です。

1バイトのアドレス空間が失われるだけで、問題になることはめったにありません。

負のポインタはありません。ポインターは常に符号なしです。また、それらが否定的である可能性がある場合、規約はアドレス空間の半分を失うことを意味します。


注:実際にアドレス空間を失うことはありません。次のようにすると、アドレス0へのポインタを取得できますchar *p = (char *)1; --p;。ヌルポインタに挙動は、標準によって定義されていないので、このシステムを持つことができp、実際に読み出しおよび書き込みアドレス0は、インクリメントアドレス付与する1等、
MM

@MattMcNabb:アドレス0が有効なハードウェアアドレスである実装では、アドレス0を読み取り、その値をxに格納する動作を完全に正当に定義char x = ((char*)0);できます。そのようなコードは、その動作を定義していない実装で未定義の動作を生成しますが、標準が何かが未定義の動作であると言っているという事実は、実装がそれが何をするかについて独自の仕様を提供することを決して禁止しません。
スーパーキャット2015年

ITYM @supercat *(char *)0。それは事実ですが、私の提案では、実装は*(char *)0他のnullポインタ操作の動作を定義する必要はありません。
MM

1
@MattMcNabb:の動作はchar *p = (char*)1; --p;、オブジェクトの最初のバイト以外へのポインタがにキャストされた後にそのシーケンスが実行されintptr_t、そのキャストの結果が偶然に値1になった場合にのみ、標準によって定義されます。その特定の場合に結果--pポインタ値にキャスト1前のバイトへのポインタを生じるintptr_t、得られていたが1
スーパーキャット2015年

3

Cは0を使用してnullポインターを表しますが、ポインター自体の値がゼロではない場合があることに注意してください。ただし、ほとんどのプログラマーは、ヌルポインターが実際には0であるシステムのみを使用します。

しかし、なぜゼロなのか?それは、すべてのシステムが共有する1つのアドレスです。また、下位アドレスはオペレーティングシステム用に予約されていることが多いため、この値はアプリケーションプログラムへの立ち入り禁止として適切に機能します。整数値をポインターに誤って割り当てると、他の何よりもゼロになる可能性が高くなります。


3
これの背後にある可能性が高い理由は、事前にゼロに初期化されたメモリを配布する方が安く、そのメモリの値が整数0、浮動小数点0.0、nullポインタなどの意味のあるものを表すと便利です。ゼロ/ nullに初期化されるCの静的データは、実行可能ファイルのスペースを占有する必要はなく、ロード時にゼロで埋められたブロックにマップされます。ゼロは、機械語でも特別な扱いを受ける場合があります。「ゼロに等しい場合の分岐」などの簡単なゼロ比較など。MIPSには、単なるゼロ定数であるダミーレジスタさえあります。
Kaz

2

歴史的に、アプリケーションのメモリ不足はシステムリソースによって占有されていました。ゼロがデフォルトのnull値になったのはその頃でした。

これは必ずしも最新のシステムに当てはまるわけではありませんが、ポインター値を何かに割り当てて、メモリー割り当てによって何が与えられているかは、依然として悪い考えです。


2

ポインタを削除した後でnullに設定しないようにして、将来「露出エラー」が削除されるようにするという議論について...

あなたが本当にこれについて本当に心配しているなら、より良いアプローチは、動作することが保証されていますが、assert()を利用することです:


...
assert(ptr && "You're deleting this pointer twice, look for a bug?");
delete ptr;
ptr = 0;
...

これには、追加のタイピングと、デバッグビルド中に1つの追加のチェックが必要ですが、ptrが「2回」削除されたときに注意してください。コメントディスカッションで指定されている代替方法では、ポインターをnullに設定しないため、クラッシュが発生しますが、成功する保証はありません。さらに悪いことに、上記とは異なり、これらの「バグ」のいずれかがシェルフに到達すると、ユーザーにクラッシュ(またはさらに悪いことに!)を引き起こす可能性があります。最後に、このバージョンでは、プログラムを引き続き実行して、実際に何が発生するかを確認できます。

私はこれが尋ねられた質問に答えないことを知っていますが、コメントを読んでいる誰かがfree()または2回削除します。可能であるこれらのいくつかのケースでは、未定義の動作をデバッグツールとして使用することは決して良い習慣ではありません。無効なポインタを削除することによって最終的に引き起こされたバグを探す必要がなかった人は、これを提案しません。これらの種類のエラーは、突き止めるのに数時間かかり、ほぼ常に、予期しない方法でプログラムに影響を与えます。これは、元の問題を追跡することは不可能ではありません。


2

多くのオペレーティングシステムがヌルポインターの表現に全ビット0を使用する重要な理由は、これがmemset(struct_with_pointers, 0, sizeof struct_with_pointers)同様の方法ですべてのポインターstruct_with_pointersをヌルポインターに設定することです。これはC標準では保証されていませんが、多くのプログラムで想定されています。


1

古いDECマシンの1つ(PDP-8だと思います)では、Cランタイムがメモリの最初のページをメモリ保護するため、そのブロックのメモリにアクセスしようとすると例外が発生します。


PDP-8にはCコンパイラがありませんでした。PDP-11にはメモリ保護機能がなく、VAXは暗黙的に0からNULLポインター逆参照を返すことで悪名高いものでした。これがどのマシンを指しているのかわかりません。
fuz

1

センチネル値の選択は任意であり、これは実際には次のバージョンのC ++(非公式には「C ++ 0x」として知られ、将来的にはISO C ++ 2011として知られる可能性が高い)の導入により対処されています。nullptrnull値のポインタを表すキーワード。C ++では、値0は、任意のPODおよびデフォルトコンストラクターを持つ任意のオブジェクトの初期化式として使用できます。これは、ポインター初期化の場合にセンチネル値を割り当てるという特別な意味があります。負の値が選択されなかった理由については、アドレスは通常0から2 Nの範囲ですある値Nの場合は-1。つまり、アドレスは通常、符号なしの値として扱われます。最大値がセンチネル値として使用された場合、メモリのサイズに応じてシステムごとに異なる必要がありますが、0は常に表現可能なアドレスです。メモリアドレス0は通常プログラムで使用できず、現在ほとんどのOSはカーネルの一部をメモリの下位ページにロードしており、そのようなページは通常、プログラム(カーネルの保存)がタッチ(逆参照)されると、障害が発生します。


1

それはいくつかの価値を持っている必要があります。明らかに、ユーザーが合法的に使用したいと思う可能性のある値を踏む必要はありません。Cランタイムはゼロで初期化されたデータ用のBSSセグメントを提供するため、ゼロを初期化されていないポインター値として解釈することにはある程度の意味があると思います。


0

OSがアドレス0への書き込みを許可することはめったにありません。OS固有のものを低メモリに固定することは一般的です。つまり、IDT、ページテーブルなどです(テーブルはRAM内にある必要があり、RAMの上部がどこにあるかを判断するよりも下部に固定する方が簡単です)。システムテーブルを自由に編集します。

これは、彼らがCを作ったときにK&Rの頭にはなかったかもしれませんが、(0 == nullはかなり覚えやすいという事実と共に)0が人気のある選択肢になっています。


これはプロテクトモードでは当てはまりません。実際、特定のLinux構成では、仮想アドレス0に書き込むことができます。
L̳o̳̳n̳̳g̳̳p̳o̳̳k̳̳e̳̳

0

0は、特定の式でさまざまな意味を持つ特別な値です。ポインタの場合は、何度も指摘されているように、「ここにデフォルトのセンチネル値を挿入する」という最も便利な方法だったためと思われます。定数式として、ポインター式のコンテキストでは、ビット単位のゼロ(つまり、すべてのビットがゼロに設定されている)と同じ意味ではありません。C ++では、NULLポインターメンバーやメンバー関数へのポインターなど、ビット単位のゼロ表現を持たない型がいくつかあります。

ありがたいことに、C ++ 0xには、「整数式のビット単位のゼロにもマップされない既知の無効なポインタを意味する式」の新しいキーワードがありますnullptr。C ++でターゲットにできるシステムがいくつかありますが、バーフィングなしでアドレス0の逆参照を許可するため、プログラマーは注意してください。


0

このスレッドにはすでに多くの良い答えがあります。0nullポインターの値を優先する理由はさまざまですが、さらに2つ追加します。

  • C ++では、ポインターをゼロ初期化すると、ポインターがnullに設定されます。
  • 多くのプロセッサでは、他の定数よりも値を0に設定するか、0と等しいか等しくないかをテストする方が効率的です。

0

これは、C / C ++でのポインターの実装に依存します。ポインターへの割り当てでNULLが同等である特定の理由はありません。


-1

これには歴史的な理由がありますが、最適化の理由もあります。

OSがメモリページを0に初期化してプロセスを提供するのは一般的です。プログラムがそのメモリページの一部をポインタとして解釈したい場合、それは0であるため、プログラムがそのポインタが初期化されていません。(これは、初期化されていないフラッシュページに適用した場合、うまく機能しません)

もう1つの理由は、多くの多くのプロセッサで、値の同等性を0にテストするのが非常に簡単であることです。追加の命令を必要とせずに行われる無料の比較である場合があり、通常、別のレジスタまたは比較する命令ストリームのリテラルとして。

ほとんどのプロセッサの安価な比較は、0未満で0に等しい符号付きです(0より大きく0以外の符号はこれらの両方によって暗示されます)。

考えられるすべての値のうち1つの値を不良または未初期化として予約する必要があるため、不良値との同等性のテストが最も安価な値にすることもできます。これは、「\ 0」で終了する文字列にも当てはまります。

この目的で0よりも大きいまたは小さい値を使用しようとすると、最終的にアドレス範囲が半分になります。


-2

定数が0代わりに使用されるNULLC年前のいくつかの穴居人の何兆によって作られた、のでNULLNILZIP、またはNADDAすべてよりもはるかに理にかなっているだろう0

しかし、メモリアドレス指定は0から始まるので、0は他のアドレスと同じように有効なアドレスではありませんか?

確かに。多くのオペレーティングシステムでは、仮想アドレス空間であってもアドレス0でのマッピングは許可されていませんが(Cは安全でない言語であると認識されており、nullポインター逆参照のバグが非常に一般的であることを反映しているため、ページ0にマップするユーザー空間コード;したがって、コールバックを呼び出してもコールバックポインターがNULLの場合、任意のコードが実行されることはありません)。

その場合、nullポインターの処理に0をどのように使用できますか?

0ポインタと比較して使用されるため、mallocの失敗時のmallocの戻り値である実装固有の値に置き換えられます。

代わりに負の数がnullにならないのはなぜですか?

これはさらに混乱します。


具体的な内容は違うと思いますが、「穴居人」などについての根底にあるのではないでしょうか。Cに進化した最も初期の形式は、intがポインターと同じサイズであるだけでなく、多くのコンテキストでintとポインターを交換可能に使用できる特定のアーキテクチャーで実行するように設計されました。ルーチンがポインターと整数57で渡されるものを予期した場合、ルーチンは57と同じビットパターンのアドレスを使用します。これらの特定のマシンでは、ヌルポインターを示すビットパターンは0だったため、int 0を渡しましたnullポインタを渡します。
スーパーキャット2013

それ以来、Cは進化して、数値やポインタのさまざまな表現を持つ他のさまざまなマシン用のプログラムを作成できるようになりました。ゼロ以外の数値定数はめったにポインタとして使用されませんでしたが、定数数値ゼロはnullポインタを表すために広く使用されました。そのような使用を禁止すると、既存のコードが壊れてしまうため、コンパイラーは、数値ゼロを、実装がNULLポインターを表すために使用するものに変換することが期待されています。
スーパーキャット2013

-4

投稿を読む前にこの段落を読んでください。私はこの投稿を読むことに興味がある人は注意深く読んでみてください、そしてもちろん、完全に理解するまで反対しないでください、ありがとう。

現在はコミュニティウィキになっているため、誰かがいずれかの概念に同意しない場合は、何が間違っているのか、その理由を明確かつ詳細に説明して修正してください。可能であれば、出典を明記するか、再現可能な証拠を提供してください。

回答

NULL == 0の根本的な要因となる可能性がある他のいくつかの理由を次に示します

  1. ゼロが偽であるため、のif(!my_ptr)代わりに直接実行できますif(my_ptr==NULL)
  2. 開始されていないグローバル整数はデフォルトですべてゼロに初期化されるため、すべてゼロのポインターは初期化されていないと見なされます。

ここで私は他の答えについて一言述べたいと思います

構文糖のためではない

構文上の砂糖のためにNULLがゼロであると言ってもあまり意味がありません。もしそうなら、配列の長さを保持するために配列のインデックス0を使用しないのはなぜですか?

実際、Cは内部実装に最もよく似た言語ですが、構文上の砂糖のためにCがゼロを選択したと言っても意味がありませんか?他の多くの言語で行われているように、ゼロをNULLにマッピングするのではなく、キーワードnullを提供します。

そのため、今日の時点では構文上の砂糖にすぎないかもしれませんが、C言語の開発者の本来の意図は構文上の砂糖ではなかったことは明らかです。

1)仕様

しかし、C仕様では定数0からヌルポインター(セクション6.3.2.3)を示し、実装定義としてNULLを定義している(C11仕様のセクション7.19、およびC99仕様の7.17)ことは事実ですが、 Cの発明者によって書かれた本「Cプログラミング言語」では、5.4節で次のように述べられています。

Cは、ゼロがデータの有効なアドレスになることは決してないことを保証します。したがって、ゼロの戻り値を使用して、異常なイベント(この場合はスペースなし)を通知できます。

ポインターと整数は交換できません。ゼロは唯一の例外です。定数ゼロはポインターに割り当てられ、ポインターは定数ゼロと比較されます。記号定数NULLは、これがポインターの特殊な値であることをより明確に示すニーモニックとして、ゼロの代わりによく使用されます。NULLはで定義されています。以降ではNULLを使用します。

(「ゼロアドレス」という言葉から)わかるように、少なくともCの作成者の当初の意図はアドレス0であり、定数ゼロではありませんでした。さらに、この抜粋から、仕様が定数ゼロは、おそらくゼロと評価される式を除外するのではなく、代わりに整数定数ゼロを含めて、キャストなしでポインターコンテキストで使用できる唯一の整数定数にします。

2)まとめ

仕様では、ゼロアドレスはゼロ定数とは異なるものとして扱うことができると明記されていませんが、そうではありません。また、ヌルポインター定数を処理するとき、実装として定義されているとは主張していません。NULLで行います定義された定数、代わりにゼロ定数及びゼロアドレスとの間の差があるかもしれないこと、それがゼロであることを示すことを特徴。

(ただし、これが当てはまる場合は、なぜNULLが実装定義されているのか不思議に思います。そのような場合、コンパイラはとにかくすべての定数を実際の実装定義NULLに変換する必要があるため、NULLも定数ゼロになる可能性があるからです。)

ただし、実際のアクションではこれは見られません。一般的なプラットフォームでは、アドレス0と定数0は同じように扱われ、同じエラーメッセージをスローします。

さらに、今日のオペレーティングシステムは、CのNULLポインターによるゼロアドレスへのアクセスを防ぐために、実際には最初のページ全体(範囲0x0000から0xFFFF)を予約しているという事実もあります(http://en.wikipedia.org/wiki/を参照)。 Zero_page、および「Jeffrey RichterおよびChristophe NasarreによるC / C ++を介したWindows(Microsoft Press発行)」)。

したがって、私は実際にそれを実際に見たと主張している人から、プラットフォームとコンパイラ、そして彼が実際に行った正確なコードを指定してください(ただし、仕様での曖昧な定義に起因しますが、プラットフォームは彼がやりたいことは何でも自由にできます)。

しかし、どうやらCの作者はこれを念頭に置いておらず、「ゼロアドレス」について話していたようです。また、「Cはそれが有効なアドレスではないことを保証します」、および「NULLは単なるニーモニック」は、本来の意図が「構文上の砂糖」ではなかったことを明確に示しています。

オペレーティングシステムのせいではない

また、いくつかの理由により、オペレーティングシステムがアドレス0へのアクセスを拒否すると主張しています。

1)Cが書かれたとき、このWikiページhttp://en.wikipedia.org/wiki/Zero_pageで確認できるように、そのような制限はありませんでした

2)事実は、Cコンパイラがメモリアドレス0にアクセスしたことです。

これは、BellLabsによる次の論文(http://www.cs.bell-labs.com/who/dmr/primevalC.html)からの事実であると思われます

2つのコンパイラは、これに対する対処方法の詳細が異なります。前の例では、関数の名前を付けることで開始点が見つかりました。後者では、開始は単に0であると見なされます。これは、最初のコンパイラがメモリマッピングを備えたマシンが作成される前に作成されたことを示します。そのため、プログラムの起点は位置0ではなく、2番目の時点ではマッピングを提供するPDP-11がありました。

(実際、今日(ウィキペディアとマイクロソフトプレスから上記の参照を引用したように)、ゼロアドレスへのアクセスを制限する理由は、CのNULLポインターが原因です!したがって、結局は逆になります!)

3)CはオペレーティングシステムやCコンパイラの記述にも使用されていることを忘れないでください。

実際、CはUNIXオペレーティングシステムを作成する目的で開発されたため、アドレスCを制限する理由にはならないようです。

(ハードウェア)コンピューターが(物理的に)アドレス0にアクセスする方法の説明

ここでもう1つ説明したい点がありますが、どのようにしてアドレス0を参照することができるのでしょうか。

少し考えてみてください。アドレスはプロセッサによってフェッチされ、メモリバス上で電圧として送信されます。これは、メモリシステムによって実際のアドレスに到達するために使用されますが、ゼロのアドレスは電圧がないことを意味しますでは、メモリシステムの物理ハードウェアはアドレス0にどのようにアクセスするのでしょうか。

答えは、アドレス0がデフォルトであること、つまり、メモリバスが完全にオフの場合、メモリシステムは常にアドレス0にアクセスできるため、実際のアドレスを指定せずに読み取りまたは書き込みを要求する(これはアドレス0の場合)は自動的にアドレス0にアクセスします。


1
私はあなたに反対票を投じませんでしたが、あなたの投稿にはいくつかの事実上の誤りがあります。オフセット0の物理メモリにはアクセスできません(すべてのスイッチがオフになっているか?
Hasturkun 2013

0と定数0に関して、これはオリジナルの本が言っていることであり、これは実際のテストが示していることですが、この2つの間に実際の違いを見つけましたか?はいの場合、どのコンパイラとプラットフォームですか?多くの回答は私が見つけていない違いがあることを示唆していますが、それらは違いを示すためのリファレンスがありません。実際、en.wikipedia.org / wiki / Zero_pageと「Jeffrey RichterとChristophe NasarreによるC / C ++を介したWindows」(Microsoft Pressが発行)によると、最初のページ全体です。nullを防ぐために、最近のコンピューターでは保護されています(実際には複数バイトを浪費しています!)
yoel halb 2013

もちろん、アドレスのビットパターンは、何を読み取るかを選択するために使用されます。一般的にそうです。とにかく、私はあなたと議論したくないのですが、なぜあなたが反対票を投じられたのかを指摘していました。
Hasturkun 2013

私はあなたの主張に同意しません。この議論を続けることにも興味はありません。
Hasturkun 2013

6
ハードウェアの主張はナンセンスです。アドレス0を読み取るには、!Chip Selectをロー、!RASをハイ、!CASをロー、!WEをハイに、すべてのアドレスラインをローに駆動します。バスがオフのとき、!CSはハイです。
MSalters 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.