31

次の例はウィキペディアからのものです。

int arr[4] = {0, 1, 2, 3};
int* p = arr + 5;  // undefined behavior

pを逆参照しない場合、なぜarr + 5だけが未定義の動作になるのですか?ポインタは整数として動作することを期待しています。ただし、逆参照されると、ポインタの値はメモリアドレスと見なされます。

4

7 に答える 7

28

これは、ポインタが整数のように動作しないためです。標準がそう言っているので、それは未定義の振る舞いです。

ただし、ほとんどのプラットフォーム(すべてではないにしても)では、配列を逆参照しない限り、クラッシュしたり、疑わしい動作に遭遇したりすることはありません。しかし、それを逆参照しない場合、追加を行う意味は何ですか?

とは言うものの、配列の終わりを超える式は技術的に100%「正しい」ものであり、C++11仕様の§5.7¶5に従ってクラッシュしないことが保証されていることに注意してください。ただし、その式の結果は指定されていません(オーバーフローにならないことが保証されています)。一方、配列の境界を2つ以上超える他の式は、明示的に未定義の動作です。

注:これは、1を超えるオフセットからの読み取りと書き込みが安全であることを意味するものではありません。その配列に属していないデータを編集している可能性あり、状態/メモリの破損を引き起こします。オーバーフロー例外が発生することはありません。

私の推測では、それは間違っていることを逆参照するだけではないので、そのようなものだと思います。また、ポインタ演算、ポインタの比較などもあります。したがって、危険な状況を列挙する代わりに、これを行わないでくださいと言う方簡単です

于 2012-05-06T19:34:26.383 に答える
19

元のx86では、このようなステートメントで問題が発生する可能性があります。16ビットコードでは、ポインタは16+16ビットです。下位16ビットにオフセットを追加する場合は、オーバーフローを処理して上位16ビットを変更する必要がある場合があります。それは遅い操作であり、避けるのが最善でした。

これらのシステムでarray_base+offsetは、オフセットが範囲内(<=配列サイズ)の場合、オーバーフローしないことが保証されていました。ただしarray+5、配列に3つの要素しか含まれていない場合はオーバーフローします。

そのオーバーフローの結果は、配列の後ろではなく、前を指すポインターを取得したことです。そして、それはRAMではなく、メモリマップドハードウェアである可能性があります。C ++標準は、ランダムなハードウェアコンポーネントへのポインターを作成した場合に何が起こるかを制限しようとはしません。つまり、実際のシステムでは未定義動作です。

于 2012-05-07T09:37:58.470 に答える
5

arrマシンのメモリスペースの最後にある場合はarr+5、そのメモリスペースの外側にある可能性があるため、ポインタタイプが値を表すことができない可能性があります。つまり、オーバーフローが発生する可能性があり、オーバーフローは未定義です。

于 2012-05-06T19:37:00.000 に答える
5

「未定義動作」は、そのコード行でクラッシュする必要があるという意味ではありませんが、結果について保証できないことを意味します。例えば:

int arr[4] = {0, 1, 2, 3};
int* p = arr + 5; // I guess this is allowed to crash, but that would be a rather 
                  // unusual implementation choice on most machines.

*p; //may cause a crash, or it may read data out of some other data structure
assert(arr < p); // this statement may not be true
                 // (arr may be so close to the end of the address space that 
                 //  adding 5 overflowed the address space and wrapped around)
assert(p - arr == 5); //this statement may not be true
                      //the compiler may have assigned p some other value

ここに投入できる例は他にもたくさんあると思います。

于 2012-05-06T19:39:58.083 に答える
2

一部のシステム、非常にまれなシステムで、名前を付けることはできませんが、そのように境界を超えてインクリメントすると、トラップが発生します。さらに、境界保護を提供する実装が存在することを可能にします...もう一度、私は1つを考えることはできませんが。

基本的に、あなたはそれをするべきではありません、そしてそれ故にあなたがするときに何が起こるかを特定する理由はありません。何が起こるかを指定すると、実装プロバイダーに不当な負担がかかります。

于 2012-05-06T20:53:48.917 に答える
0

この結果は、x86のセグメントベースのメモリ保護によるものです。この保護は、ポインターアドレスをインクリメントして格納する場合と同様に正当化されると思います。つまり、コード内の将来の時点で、ポインターを逆参照して値を使用することになります。したがって、コンパイラは、他の人のメモリ位置を変更したり、コード内の他の人が所有しているメモリを削除したりするような状況を避けたいと考えています。このようなシナリオを回避するために、コンパイラは制限を設けています。

于 2018-10-15T07:06:18.003 に答える
0

ハードウェアの問題に加えて、別の要因は、さまざまな種類のプログラミングエラーをトラップしようとする実装の出現でした。このような実装の多くは、プログラムが使用しないことがわかっている構成をトラップするように構成されている場合に最も役立ちますが、C標準で定義されていても、標準の作成者は、次のような構成の動作を定義することを望んでいませんでした。 -多くのプログラミング分野で-エラーの兆候があります。

多くの場合、ポインタ演算を使用して意図しないオブジェクトのアドレスを計算するアクションをトラップする方が、ポインタを使用して識別したストレージにアクセスできないという事実を記録するよりもはるかに簡単ですが、ポインタを変更できるように変更できます。他のストレージにアクセスします。より大きな(2次元)配列内の配列の場合を除いて、実装では、すべてのオブジェクトの終わりを「過ぎた」スペースを予約できます。のようなものが与えられた場合doSomethingWithItem(someArray+i);、実装は、配列の要素または最後の要素のすぐ先のスペースのいずれかを指していないアドレスを渡そうとする試みをトラップする可能性があります。someArray余分な未使用要素のための予約済みスペースの割り当ての場合、およびdoSomethingWithItem()ポインタを受け取るアイテムにのみアクセスする場合、実装は比較的安価に、上記のコードのトラップされていない実行が、最悪の場合、他の方法で使用されていないストレージにアクセスできるようにすることができます。

「過去」のアドレスを計算する機能により、境界チェックが他の方法よりも難しくなります(最も一般的な誤った状況doSomethingWithItem()は、配列の終わりを過ぎたところにポインターを渡すことですが、doSomethingWithItem逆参照を試みない限り、動作は定義されます。そのポインタ-呼び出し元が証明できない可能性があるもの)。標準では、ほとんどの場合、コンパイラが配列のすぐ先のスペースを予約できるため、このような許可により、実装はトラップされていないエラーによって引き起こされる損傷を制限できます。これは、より一般化されたポインタ演算が許可されている場合は実用的ではない可能性があります。

于 2018-10-15T16:54:28.230 に答える