2

Michael Burrthis answerへのコメントから学んだように、C標準では、配列の最初の要素を超えるポインタからの整数減算はサポートされていません。 the combined C99 + TC1 + TC2のセクション6.5.6(PDF)からどのCコンパイラにポインタ減算アンダーフローがありますか?

ポインタオペランドと結果ポイント同じ配列オブジェクトの要素に、または配列オブジェクトの最後の要素過去1両方の場合、評価は、オーバーフローを生じてはならない。それ以外の場合、その動作は不確定です。

私はポインタ算術が大好きですが、これまで私が心配していたことはありませんでした。私は常に与えられたと仮定しています:

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

これはc == aです。

私はこれまでにこのようなことをしていて、噛まなかったと信じていますが、これまでのさまざまなコンパイラの優しさから、標準では、私が思ったようにポインタの算術演算を行う必要があります。

私の質問は、どれくらい共通点ですか?私のために親切なことをしないコンパイラがよく使われていますか?ポインタの算術演算は、配列の境界を越えて正しく定義されていますか?

+3

CPUアーキテクチャのようにコンパイラの問題ではありません。そこにはいくつかのあいまいなメモリモデルがあります。一般的に、すべてのシステムでプレーンな線形メモリを想定することはできません。ポインターをメモリアドレスと見なさないでください。彼らはそうではありません。彼らには別々の制限があります。 – jalf

答えて

4

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

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

+0

私は、OPが*なぜ*これほど真実であるかと不思議に思っています。 Windows 3.0用のアプリケーションを開発することの "喜び"を経験したことがないなら、今日の簡単な方法を理解できないかもしれません; – RBerteig

+0

実際に私はWindows 3.0用のプログラムを作成しました。これまでのところ、ファイルマネージャはファイルタイプを1つのプログラムに関連付けることしかできませんでした。私は、ユーザーがファイルタイプごとに複数のプログラムを追加できるハンドラを書いた。ユーザはそのプログラムにファイルを関連づけ、そのプログラムを右クリックすることにより、そのファイルタイプのプログラムの通関リストからユーザが選択することが可能になった。 – tpdi

+1

MSCコンパイラをDOSボックスで確実に実行することができなかったので、コンパイルするためにDOSに戻って、適切なエディタを実行しなければならないことを覚えています。さらに、バグがあっても、Windowsを終了するには3本の敬礼が必要で、DOSプロンプトはそれ以降の最初のストップでした...本当の喜びは、紙やテキストエディタでダイアログボックスのレイアウトを設計し、彼らがどのように見えるかを見るためにコンパイルに座っている... – RBerteig

7

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

保護モードでは、セグメントレジスタは実際にはメモリ記述子のテーブルへのインデックスでした。典型的なDOS拡張ランタイムは、通常、リアルモードのように多くのセグメントを扱うことができるように、実際のモードからの移植コードを簡単に扱えるように配置します。しかし、それにはいくつかの欠陥がありました。主に、の前にの割り当てが割り当ての一部でないため、その記述子は有効ではない可能性があります。

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

同様の問題は、割り当てより1バイト後に発生する可能性があります。ポインタ上の最後の++がセグメントレジスタに引き継がれ、新しいディスクリプタをロードする可能性があります。この場合、メモリアロケータがの1つの安全な記述子を割り当てられた範囲の最後を過ぎて配置できると予想するのが合理的ですが、それ以上のアレンジを期待するのは無理でしょう。

+0

http://www.faktoider.nu/640kb_eng.html :) – unwind

+0

ありがとうございました...私はフレーズを引用しなかったことに気付くでしょうそれのためにBillを非難する。真剣に誰からも言われていないかもしれませんが、大規模な記憶のために*支払う必要があった人には慣れていません.... – RBerteig

0

ZETA-C(TIエクスプローラの場合)ポインタは配列とインデックスまたは置換された配列、IIRCとして実装されているので、おそらくあなたの例はうまくいかないでしょう。 zcprim>pointer-subtractからzcprim.lispに移動して、動作を確認します。これが標準で正しいのかどうかは分かりませんが、私はそれが印象を受けました。

1

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

この仮定を破る人気の高い商業的品質と使用されたライブラリがあります。ポインタアルゴリズムを使用して非常に複雑なハッシュ構造を実装するのは、Judy Trees Libraryです。