2

そのため、 Michael Burrのこの回答へのコメントから学んだように、C 標準では、配列の最初の要素 (割り当てられたメモリが含まれていると思われます) を過ぎたポインターからの整数減算はサポートされていません。

結合された C99 + TC1 + TC2 (pdf)のセクション 6.5.6 から:

ポインターオペランドと結果の両方が同じ配列オブジェクトの要素を指している場合、または配列オブジェクトの最後の要素の 1 つ後ろを指している場合、評価はオーバーフローを生成しません。それ以外の場合、動作は未定義です。

私はポインター演算が大好きですが、これはこれまで心配したことはありませんでした。私はいつもそれを仮定してきました:

 int a[1];
 int * b = a - 3;
 int * c = b + 3;

それc == a

だから、私は以前にそのようなことをしたことがあり、噛まれたことはないと信じていますが、それは私が働いてきたさまざまなコンパイラの親切さによるものに違いありません.私が思っていたようにポインター演算を機能させます。

だから私の質問は、それはどのくらい一般的ですか? 私のためにその親切をしない一般的に使用されるコンパイラはありますか? 配列の境界を超えた適切なポインター演算は事実上の標準ですか?

4

4 に答える 4

7

MSDOS FAR ポインターにはこのような問題がありましたが、通常はリア​​ルモードでセグメント レジスタとオフセット レジスタのオーバーラップを「巧妙に」使用することでカバーされていました。その結果、16 ビット セグメントは 4 ビット左にシフトされ、16 ビット オフセットに追加され、1MB をアドレス指定できる 20 ビットの物理アドレスが与えられました。 640KB もの RAM。;-)

プロテクト モードでは、セグメント レジスタは実際にはメモリ記述子のテーブルへのインデックスでした。典型的な DOS 拡張ランタイムは、通常、多くのセグメントがリアル モードの場合と同じように処理できるように調整します。これにより、リアル モードからのコードの移植が容易になりました。しかし、それにはいくつかの欠陥がありました。主に、割り当て前のセグメントは割り当ての一部ではなかったため、その記述子は有効でさえない可能性があります。

プロテクト モードの 80286 では、無効なディスクリプタをロードする値をセグメント レジスタにロードすると、そのディスクリプタが実際にメモリを参照するために使用されたかどうかにかかわらず、例外が発生しました。

割り当てを 1 バイト過ぎたところで、同様の問題が発生する可能性があります。ポインタの最後の ++ がセグメント レジスタに持ち越され、新しい記述子がロードされた可能性があります。この場合、メモリ アロケータが割り当てられた範囲の末尾を超えて1 つの安全な記述子を配置できると期待するのは合理的ですが、それ以上の記述子を配置することを期待するのは合理的ではありません。

于 2009-04-24T06:57:10.030 に答える
4

これは、標準によって「定義された実装」ではなく、標準によって「未定義」です。つまり、それをサポートするコンパイラを当てにすることはできません。「まあ、このコードはコンパイラ X で安全だ」とは言えません。未定義の動作を呼び出すことにより、プログラムは未定義になります。

実際的な答えは、「どのように (どこで、いつ、どのコンパイラで) これを回避できるか」ではありません。実際の答えは「これをしないでください」です。

于 2009-04-24T06:56:17.533 に答える
1

もう 1 つの理由は、ポインターが常に割り当てられた範囲内にあり、そうでない場合はいつでもメモリを解放できると想定するオプションの保守的なガベージ コレクター (boehm-weiser GC など) があることです。

この仮定を破る 1 つの一般的な商用品質の使用済みライブラリがあり、ポインター アルゴリズムを使用して非常に複雑なハッシュ構造を実装する HP の Judy Trees ライブラリです。

于 2009-11-06T22:57:03.710 に答える
0

TI Explorer 用のZETA-C 。ポインターは配列とインデックスまたは変位配列、IIRC として実装されるため、この例はおそらく機能しません。から始めてzcprim>pointer-subtractzcprim.lisp動作がどうなるかを把握します。これが標準で正しいかどうかはわかりませんが、そうだったという印象を受けます。

于 2009-04-24T21:13:52.823 に答える