16

ポインター演算などを使用する C のメモリ モデルは、フラットなアドレス空間をモデル化しているように見えます。16 ビット コンピュータは、セグメント化されたメモリ アクセスを使用していました。16 ビット C コンパイラは、この問題にどのように対処し、C プログラマの観点からフラットなアドレス空間をシミュレートしたのでしょうか? たとえば、次のコードを 8086 でコンパイルすると、おおよそどのアセンブリ言語命令になりますか?

long arr[65536];  // Assume 32 bit longs.
long i;
for(i = 0; i < 65536; i++) {
    arr[i] = i;
}
4

6 に答える 6

15

16 ビット C コンパイラは、この問題にどのように対処し、C プログラマの観点からフラットなアドレス空間をシミュレートしたのでしょうか?

彼らはしませんでした。代わりに、C プログラマーがセグメンテーションを認識できるようにし、複数のタイプのポインター ( nearfar、およびhuge) を持つことで言語を拡張しました。nearポインターはオフセットのみでしたが、およびfarポインターhugeはセグメントとオフセットを組み合わせたものでした。デフォルトのポインタ型がnearかfarかを決定するメモリモデルを設定するコンパイラオプションがありました。

Windows コードでは、今日でもLPCSTR(for const char*) のような typedef をよく見かけます。「LP」は 16 ビット時代からの名残りです。「ロング(ファー)ポインター」の略です。

于 2010-09-30T03:29:28.943 に答える
10

真の 16 ビット環境では、任意のアドレスに到達する 16 ビット ポインターが使用されます。例には、PDP-11、6800 ファミリ (6802、6809、68HC11)、および 8085 が含まれます。これは、単純な 32 ビット アーキテクチャと同様に、クリーンで効率的な環境です。

80x86 ファミリは、いわゆる「リアル モード」でのハイブリッド 16 ビット/20 ビット アドレス空間、つまりネイティブ 8086 アドレス空間を強制しました。これに対処するための通常のメカニズムは、ポインターの型をnear(16 ビット ポインター) とfar(32 ビット ポインター) の 2 つの基本型に拡張することでした。コードおよびデータ ポインタのデフォルトは、「メモリ モデル」によってまとめて設定できます: tinysmallcompactmediumfarおよびhuge(一部のコンパイラはすべてのモデルをサポートしていません)。

tinyメモリ モデルは、スペース全体 (コード + データ + スタック) が 64K 未満の小さなプログラムに役立ちます。すべてのポインターは (デフォルトで) 16 ビットまたはnear; ポインターは、プログラム全体のセグメント値に暗黙的に関連付けられます。

このsmallモデルは、データ + スタックが 64K 未満で、同じセグメントにあることを前提としています。コード セグメントにはコードのみが含まれるため、最大メモリ フットプリントが 128K の場合、最大 64K も含めることができます。コード ポインタはnear、暗黙的に CS (コード セグメント) に関連付けられています。データ ポインタもnearDS (データ セグメント) に関連付けられています。

モデルには最大 64Kのmediumデータ + スタック (小規模のようなもの) がありますが、任意の量のコードを含めることができます。データ ポインタは 16 ビットで、暗黙的にデータ セグメントに関連付けられています。コード ポインターは 32 ビットfarポインターであり、リンカがコード グループをどのようにセットアップしたかによってセグメント値を持ちます (厄介な簿記の手間)。

モデルはメディアの補完です。compactコードは 64K 未満ですが、データの量は問いません。データ ポインタはfarで、コード ポインタはnearです。

largeまたはモデルではhuge、ポインタのデフォルトのサブタイプは 32 ビットまたはfar. 主な違いは、巨大なポインターは常に自動的に正規化されるため、それらをインクリメントすると 64K のラップ アラウンドの問題が回避されることです。これを参照してください。

于 2010-09-30T03:03:01.030 に答える
10

C メモリ モデルは、フラットなアドレス空間を意味するものではありません。それは決してしませんでした。実際、C 言語の仕様は、フラットでないアドレス空間を許可するように特別に設計されています。

セグメント化されたアドレス空間を使用した最も単純な実装では、最大の連続オブジェクトのサイズはセグメントのサイズ (16 ビット プラットフォームでは 65536 バイト) によって制限されます。これは、そのsize_tような実装では 16 ビットになり、許可された最大サイズよりも大きいサイズのオブジェクトを宣言しようとしているため、コードがコンパイルされないことを意味します。

より複雑な実装では、いわゆるヒュージメモリ モデルがサポートされます。おわかりのように、セグメント化されたメモリ モデルでは、任意のサイズの連続したメモリ ブロックをアドレス指定するのに問題はありません。ポインタ演算に余分な労力が必要なだけです。そのため、ヒュージ メモリ モデル内では、実装によって余分な作業が行われるため、コードが少し遅くなりますが、同時に、事実上あらゆるサイズのオブジェクトのアドレス指定が可能になります。したがって、コードは完全に正常にコンパイルされます。

于 2010-09-30T03:22:45.610 に答える
7

DOS 16 ビットでは、それができた覚えがありません。それぞれが 64K (バイト) のものを複数持つことができます (セグメントを調整してオフセットをゼロにすることができるため) が、単一の配列で境界を越えることができるかどうかは覚えていません。32 ビットの DOS プログラムを (386 または 486 プロセッサで) コンパイルできるようになるまでは、好きなように好きなだけ割り当てて好きなだけ配列に到達できるフラットなメモリ空間は実現しませんでした。おそらく、Microsoft と Borland 以外のオペレーティング システムとコンパイラは、64k バイトを超えるフラットな配列を生成する可能性があります。Win16 win32 がヒットするまで、その自由を覚えていません。おそらく私の記憶は錆びてきているのでしょう...とにかくメガバイトのメモリを持っていることは幸運か金持ちでした.256k バイトまたは 512k バイトのマシンは前代未聞ではありません. あなたのフロッピードライブは、最終的に 1.44 メガから 1 メガの数分の 1 メガを持っていました。

地球上に登録されているすべてのドメイン名の DNS データベース全体をダウンロードできるようになったとき、私が DNS について学んだ特定の課題を覚えています。サイト。そのファイルは 35 メガバイトで、私のハードディスクは 100 メガバイトで、DOS と Windows がその一部を食いつぶしていました。おそらく 1 メガバイトか 2 メガバイトのメモリがあり、当時は 32 ビットの DOS プログラムを実行できたかもしれません。複数のパスで行った ascii ファイルを解析したいのですが、パスごとに出力を別のファイルに移動する必要があり、前のファイルを削除して次のファイル用のディスクのスペースを確保する必要がありました。標準のマザーボードに 2 つのディスク コントローラがあり、1 つはハードディスク用、もう 1 つは cdrom ドライブ用です。

C で 64k バイトを読み取るという問題さえありました。16 ビット int で読み取りたいバイト数を fread に渡しました。これは、65536 バイトではなく 0 から 65535 を意味し、偶数サイズのセクターを読み取らなかった場合、パフォーマンスが劇的に低下したため、パフォーマンスを最大化するために一度に 32k バイトを読み取るだけで、64k は dos32 の時代になってようやく fread に渡された値が 32 ビットの数値になり、コンパイラが上位 16 ビットのみを切り捨てるつもりはないと確信するまで来ませんでした。下位 16 ビットを使用します (十分な数のコンパイラ/バージョンを使用した場合によく発生します)。現在、16 ビットから 32 ビットへの移行で行ったように、32 ビットから 64 ビットへの移行でも同様の問題が発生しています。最も興味深いのは、16 ビットから 32 ビットの int に変更するとサイズが変わることを学んだ私のような人々のコードです。しかし、unsigned char と unsigned long はそうではなかったため、プログラムが 16 ビットと 32 ビットの両方でコンパイルおよび動作するように、int を適応させ、めったに使用しませんでした。(その世代の人々のコードは、それを生き抜いて同じトリックを使用した他の人々よりも際立っています)。しかし、32 から 64 への移行では逆であり、uint32 型宣言を使用するようにリファクタリングされていないコードは苦しんでいます。

入ってきたばかりのwallykの答えを読むと、ラップアラウンドされた巨大なポインターがベルを鳴らし、常に巨大にコンパイルできるとは限りません。small は、現在快適に使用できるフラット メモリ モデルであり、セグメントについて心配する必要がないため、今日と同様に簡単でした。そのため、可能な場合は小さくコンパイルすることが望ましいものでした。まだ多くのメモリ、ディスク、またはフロッピー スペースがなかったので、通常はそれほど大きなデータを処理していませんでした。

別の回答に同意すると、セグメント オフセットは 8088/8086 インテルでした。全世界はまだインテルに支配されていなかったので、フラットなメモリ空間を持っているか、おそらくハードウェア (プロセッサの外側) で問題を解決するために他のトリックを使用した他のプラットフォームがありました. セグメント/オフセットのおかげで、Intel は 16 ビットの処理におそらく必要以上に長く乗ることができました。セグメント/オフセットには、クールで興味深いことがいくつかありましたが、それは他の何よりも苦痛でした。生活を簡素化し、フラットなメモリ空間に住んでいたか、セグメントの境界について常に心配していました。

于 2010-09-30T03:48:44.453 に答える
4

古い x86 で実際にアドレス サイズを特定するのは、ちょっと難しい作業です。アドレスに対して実行できる算術演算は 16 ビット レジスタに収まる必要があるため、16 ビットと言えます。実際のアドレスは 16 ビットの汎用レジスタと 16 ビットのセグメント レジスタ (32 ビットはすべて有効) に対して計算されるため、32 ビットとも言えます。セグメント レジスタは 4 ビット左にシフトされ、ハードウェア アドレス指定のために gp レジスタに追加されるため、単に 20 ビットであるとも言えます。

これらのどれを選択しても、実際にはそれほど問題ではありません。なぜなら、それらはすべて c 抽象マシンのほぼ等しい近似値だからです。一部のコンパイラでは、コンパイルごとに使用していたメモリ モデルを選択できますが、他のコンパイラでは 32 ビット アドレスを想定し、16 ビットをオーバーフローする可能性のある操作がそのケースを正しく処理する命令を発行することを注意深くチェックします。

于 2010-09-30T03:00:54.153 に答える
2

このウィキペディアのエントリを確認してください。Far ポインターについて。基本的にセグメントとオフセットを指定することができ、別のセグメントにジャンプすることができます。

于 2010-09-30T02:58:12.380 に答える