誰かが私にこれらのポインタを適切な例で説明できますか...そしてこれらのポインタがいつ使用されるか?
6 に答える
主な例は、IntelX86アーキテクチャです。
Intel 8086は、内部的には16ビットプロセッサでした。すべてのレジスタは16ビット幅でした。ただし、アドレスバスは20ビット幅(1 MiB)でした。これは、アドレス全体をレジスターに保持できず、最初の64kiBに制限されることを意味しました。
Intelのソリューションは、内容が4ビット左にシフトされてアドレスに追加される16ビットの「セグメントレジスタ」を作成することでした。例えば:
DS ("Data Segment") register: 1234 h
DX ("D eXtended") register: + 5678h
------
Actual address read: 179B8h
これにより、64kiBセグメントの概念が生まれました。したがって、「near」ポインタはDXレジスタ(5678h)の内容であり、DSレジスタがすでに正しく設定されていない限り無効になりますが、「far」ポインタは32ビット(12345678h、DSの後にDX)であり、常に機能します(ただし、2つのレジスタをロードし、完了したらDSレジスタを復元する必要があるため低速でした)。
(以下のスーパーキャットノートのように、オーバーフローしたDXへのオフセットは、DSに追加される前に「ロールオーバー」して、最終的なアドレスを取得します。これにより、16ビットオフセットが64kiBセグメントの任意のアドレスにアクセスできるようになりました。一部の命令で16ビットの相対オフセットアドレス指定を使用する他のアーキテクチャで行われているように、DXがポイントした場所から±32kiB。)
ただし、値は異なるが同じアドレスを指す2つの「far」ポインタを使用できることに注意してください。たとえば、ファーポインタ100079B8hは、12345678hと同じ場所を指します。したがって、farポインターでのポインター比較は無効な操作でした。ポインターは異なる可能性がありますが、それでも同じ場所を指します。
ここで、Mac(当時はMotorola 68000プロセッサを搭載)はそれほど悪くないと判断したので、巨大なポインタを見逃しました。IIRC、これらは、2番目の例のように、セグメントレジスタ内の重複するすべてのビットが0であることを保証する単なる遠いポインタでした。
Motorolaは、64 kiBに制限されていたため、6800シリーズのプロセッサでこの問題を抱えていませんでした。68000アーキテクチャを作成したとき、32ビットレジスタに直接移行したため、近く、遠く、または巨大なポインタは必要ありませんでした。 。(代わりに、アドレスの下位24ビットのみが実際に重要であるという問題があったため、一部のプログラマー(Appleで有名)は上位8ビットを「ポインターフラグ」として使用し、アドレスバスが32ビット(4 GiB)に拡張されたときに問題を引き起こしました。 。)
Linus Torvaldsは、アドレスが32ビットで、セグメントレジスタがアドレスの上位半分であり、追加が不要な「プロテクトモード」を提供する80386まで持ちこたえ、プロテクトを使用するために最初からLinuxを作成しました。モードのみで、奇妙なセグメントのものはありません。そのため、Linuxでニアポインターとファーポインターのサポートがありません(そして、新しいアーキテクチャを設計している会社がLinuxサポートを必要とする場合にそれらに戻ることはありません)。そして、彼らはロビンのミンストレルを食べました、そして多くの喜びがありました。(わーい...)
遠いポインターと巨大なポインターの違い:
デフォルトでわかっているように、ポインタはnear
次のようになります。int *p
はnear
ポインタです。near
16ビットコンパイラの場合、ポインタのサイズは2バイトです。そして、サイズがコンパイラごとに異なることはすでによくわかっています。それらは、参照しているポインタのアドレスのオフセットのみを格納します。オフセットのみで構成されるアドレスの範囲は0〜64Kバイトです。
Far
およびhuge
ポインタ:
Far
ポインタのhuge
サイズは4バイトです。これらは、ポインタが参照しているアドレスのセグメントとオフセットの両方を格納します。では、それらの違いは何ですか?
ファーポインタの制限:
算術演算を適用して、指定された遠方アドレスのセグメントアドレスを変更または変更することはできません。つまり、算術演算子を使用すると、あるセグメントから別のセグメントにジャンプすることはできません。
セグメントアドレスをインクリメントする代わりに、オフセットアドレスの最大値を超えてfarアドレスをインクリメントすると、オフセットアドレスが循環順に繰り返されます。これはラッピングとも呼ばれます。つまり、オフセットが0xffff
であり、1を追加すると、それが0x0000
なります。同様に、0x0000
1を減らす0xffff
と、セグメントに変更がないことを覚えておいてください。
今、私は巨大なポインターと遠いポインターを比較するつもりです:
1.farポインターがインクリメントまたはデクリメントされると、ポインターのオフセットのみが実際にインクリメントまたはデクリメントされますが、巨大なポインターの場合、セグメントとオフセット値の両方が変更されます。
ここから抜粋した次の例を考えてみましょう。
int main()
{
char far* f=(char far*)0x0000ffff;
printf("%Fp",f+0x1);
return 0;
}
その場合、出力は次のようになります。
0000:0000
セグメント値に変更はありません。
そして巨大なポインタの場合:
int main()
{
char huge* h=(char huge*)0x0000000f;
printf("%Fp",h+0x1);
return 0;
}
出力は次のとおりです。
0001:0000
これは、インクリメント操作により、オフセット値だけでなく、セグメント値も変化するためです。つまり、ポインタの場合はセグメントが変化しませんが、far
ポインタの場合はhuge
、あるセグメントから別のセグメントに移動できます。
2.関係演算子が遠いポインターで使用される場合、オフセットのみが比較されます。言い換えると、関係演算子は、比較されるポインターのセグメント値が同じである場合にのみ遠いポインターで機能します。far
そして、巨大な場合、これは起こりません、実際に絶対アドレスの比較が行われます。ポインタの例の助けを借りて理解しましょう:
int main()
{
char far * p=(char far*)0x12340001;
char far* p1=(char far*)0x12300041;
if(p==p1)
printf("same");
else
printf("different");
return 0;
}
出力:
different
ポインタhuge
内:
int main()
{
char huge * p=(char huge*)0x12340001;
char huge* p1=(char huge*)0x12300041;
if(p==p1)
printf("same");
else
printf("different");
return 0;
}
出力:
same
説明:両方の絶対アドレスp
とp1
is 12341
(1234*10+1
または)が表示されますが、ポインター1230*10+41
の場合はオフセットのみが比較されるため、最初のケースでは等しいとは見なされません。つまり、 。これは誤りです。far
0001==0041
また、巨大なポインタの場合、比較操作は等しい絶対アドレスに対して実行されます。
遠いポインタは正規化されませんが、
huge
ポインタは正規化されます。正規化されたポインターは、セグメント内にできるだけ多くのアドレスを持つポインターです。つまり、オフセットが15を超えることはありません。正規
0x1234:1234
化された形式が0x1357:0004
(絶対アドレスは13574
)であるとします。巨大なポインターは、何らかの算術演算が実行された場合にのみ正規化され、割り当て中には正規化されません。int main() { char huge* h=(char huge*)0x12341234; char huge* h1=(char huge*)0x12341234; printf("h=%Fp\nh1=%Fp",h,h1+0x1); return 0; }
出力:
h=1234:1234 h1=1357:0005
説明:
huge
代入の場合、ポインタは正規化されていませんが、算術演算を実行すると正規化されます。つまり、h
is1234:1234
とh1
is1357:0005
は正規化されています。4.正規化のため、巨大なポインターのオフセットは16未満ですが、遠いポインターの場合はそうではありません。
私が言いたいことを理解するために例を見てみましょう:
int main() { char far* f=(char far*)0x0000000f; printf("%Fp",f+0x1); return 0; }
出力:
0000:0010
huge
ポインタの場合:
int main()
{
char huge* h=(char huge*)0x0000000f;
printf("%Fp",h+0x1);
return 0;
}
Output:
0001:0000
説明:farポインターを1インクリメントすると、次のようになり0000:0010
ます。巨大ポインターを1インクリメントすると0001:0000
、オフセットが15を超えることができないため、つまり正規化されます。
昔は、Turbo Cのマニュアルによると、コードとデータ全体が1つのセグメントに収まる場合、ニアポインターはわずか16ビットでした。ファーポインタは、セグメントとオフセットで構成されていましたが、正規化は実行されませんでした。そして、巨大なポインタが自動的に正規化されました。2つの遠いポインタはメモリ内の同じ場所を指していると考えられますが、異なる可能性がありますが、同じメモリ位置を指している正規化された巨大なポインタは常に等しくなります。
一部のアーキテクチャでは、システム内のすべてのオブジェクトを指すことができるポインタは、有用なサブセットを指すことができるポインタよりも大きく、動作が遅くなります。多くの人が16ビットx86アーキテクチャに関連する回答をしました。16ビットシステムではさまざまな種類のポインターが一般的でしたが、実装方法によっては、64ビットシステムでニア/フィアの区別が再び現れる可能性があります(多くの開発システムが64ビットポインターに移行しても驚かないでしょう。多くの場合、それは非常に無駄になるという事実にもかかわらず、すべて。
多くのプログラムでは、メモリ使用量を2つのカテゴリに分類するのは非常に簡単です。合計するとかなり少量(64Kまたは4GB)になりますが、頻繁にアクセスされる小さなものと、合計するとはるかに大量になる可能性のある大きなものです。 、しかし、それほど頻繁にアクセスする必要はありません。アプリケーションが「大きなもの」領域のオブジェクトの一部を操作する必要がある場合、アプリケーションはその部分を「小さなもの」領域にコピーして操作し、必要に応じて書き戻します。
一部のプログラマーは、「近い」メモリと「遠い」メモリを区別する必要があることに不満を持っていますが、多くの場合、そのような区別を行うことで、コンパイラははるかに優れたコードを生成できます。
(注:多くの32ビットシステムでも、追加の命令なしでメモリの特定の領域に直接アクセスできますが、他の領域にはアクセスできません。たとえば、68000またはARMの場合、レジスタがグローバル変数ストレージを指すようにします。そのレジスタの最初の32K(68000)または2K(ARM)内の任意の変数を直接ロードすることが可能です。他の場所に格納されている変数をフェッチするには、アドレスを計算するための追加の命令が必要になります。より頻繁に使用される変数を優先領域に配置します。コンパイラに通知すると、より効率的なコード生成が可能になります。
この回答のすべてのものは、古い8086および80286セグメント化メモリモデルにのみ関連しています。
near:64kセグメントの任意のバイトをアドレス指定できる16ビットポインタ
far:セグメントとオフセットを含む32ビットポインタ。セグメントはオーバーラップする可能性があるため、2つの異なるfarポインタが同じアドレスを指す可能性があることに注意してください。
巨大:セグメントが「正規化」されている32ビットポインター。同じ値を持たない限り、2つの遠いポインターが同じアドレスを指すことはありません。
ティー:ジャムとパンの飲み物。
それは私たちをdohohohohに戻すでしょう
そして、これらのポインタが使用されるのはいつですか?
1980年代と90年代に32ビットWindowsが普及するまで、
この用語は、16ビットアーキテクチャで使用されていました。
16ビットシステムでは、データは64Kbセグメントに分割されていました。ロード可能な各モジュール(プログラムファイル、動的にロードされるライブラリなど)には、最大64Kbのデータのみを格納できるデータセグメントが関連付けられていました。
NEARポインターは、16ビットストレージを備えたポインターであり、現在のモジュールデータセグメント内のデータ(のみ)を参照していました。
要件として64Kbを超えるデータを持つ16ビットプログラムは、FARポインター(上位16ビットのデータセグメントIDと下位16ビットのそのデータセグメントへのポインター)を返す特別なアロケーターにアクセスできます。
さらに大規模なプログラムでは、64Kbを超える連続したデータを処理する必要があります。巨大なポインターは遠いポインターとまったく同じように見えます-32ビットのストレージがあります-しかし、アロケーターは、データセグメントセレクターをインクリメントするだけで次の64Kbのデータチャンクができるように、連続するIDを持つデータセグメントの範囲を配置するように注意を払っています到達しました。
基盤となるCおよびC++言語標準は、メモリモデルでこれらの概念を公式に認識していません。CまたはC++プログラムのすべてのポインタは同じサイズであると想定されています。したがって、NEAR、FAR、およびHUGE属性は、さまざまなコンパイラベンダーによって提供された拡張機能でした。