web-dev-qa-db-ja.com

近く、遠く、そして巨大なポインターは何ですか?

誰かが私にこれらのポインタを適切な例で説明できますか...そしてこれらのポインタがいつ使用されるか?

29
2easylogic

昔は、Turbo Cのマニュアルによると、コードとデータ全体が1つのセグメントに収まる場合、ニアポインターはわずか16ビットでした。ファーポインタは、セグメントとオフセットで構成されていましたが、正規化は実行されませんでした。そして、巨大なポインタが自動的に正規化されました。 2つの遠いポインタはメモリ内の同じ場所を指していると考えられますが、異なる可能性がありますが、同じメモリ位置を指している正規化された巨大なポインタは常に等しくなります。

12
PP.

主な例は、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)の内容にすぎず、「far」ポインタが32ビットであるのに対し、DSレジスタがすでに正しく設定されていない限り、無効になります。 (12345678h、DSの後にDXが続く))常に機能します(ただし、2つのレジスタをロードしてから、完了時にDSレジスタを復元する必要があるため、速度は遅くなりました) 。

(以下のスーパーキャットノートのように、オーバーフローしたDXへのオフセットは「ロールオーバー」します DSに追加されて最終アドレスを取得します。これにより、16ビットのオフセットが可能になりました。一部の命令で16ビットの相対オフセットアドレス指定を使用する他のアーキテクチャで行われているように、DXがポイントした場所から±32 kiBであった部分だけでなく、64kiBセグメントの任意のアドレスにアクセスします。)

ただし、値は異なるが同じアドレスを指す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サポートが必要な場合に新しいアーキテクチャを設計している会社がそれらに戻ることはありません)。そして、彼らはロビンのミンストレルを食べました、そして多くの喜びがありました。 (わーい...)

28
Mike DeSimone

遠いポインターと巨大なポインターの違い:

デフォルトでは、ポインタはnearです。たとえば、int *pnearポインタです。 nearポインタのサイズは、16ビットコンパイラの場合は2バイトです。そして、サイズがコンパイラごとに異なることはすでによくわかっています。それらは、それが参照しているポインタのアドレスのオフセットのみを格納します。オフセットのみで構成されるアドレスの範囲は0〜64Kバイトです。

Farおよびhugeポインタ:

Farおよびhugeポインターのサイズは4バイトです。これらは、ポインタが参照しているアドレスのセグメントとオフセットの両方を格納します。それでは、それらの間の違いは何ですか?

遠方ポインタの制限:

算術演算を適用して、指定された遠方アドレスのセグメントアドレスを変更または変更することはできません。つまり、算術演算子を使用すると、あるセグメントから別のセグメントにジャンプすることはできません。

セグメントアドレスをインクリメントする代わりに、オフセットアドレスの最大値を超えてファーアドレスをインクリメントすると、オフセットアドレスが循環順に繰り返されます。これはラッピングとも呼ばれます。つまり、オフセットが0xffffで1を追加すると、それは0x0000になり、同様に0x0000を1減らすと、0xffffになり、セグメントに変更がないことを覚えておいてください。

次に、巨大なポインターと遠いポインターを比較します:

1.farポインタがインクリメントまたはデクリメントされると[〜#〜] only [〜#〜]ポインタのオフセットは実際にはインクリメントまたはデクリメントされますが、巨大なポインタの場合、セグメント値とオフセット値の両方が変更されます。

[〜#〜] here [〜#〜] から抜粋した次の例を考えてみましょう:

 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ポインタの場合は1つのセグメントから移動できます。別に。

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

説明:pp1の両方の絶対アドレスは123411234*10+1または1230*10+41)ですが、farポインターの場合はオフセットのみが比較されるため、最初のケースでは等しいとは見なされません。 0001==0041かどうかをチェックします。これは誤りです。

また、巨大なポインタの場合、比較操作は等しい絶対アドレスに対して実行されます。

  1. 遠方ポインタは正規化されませんが、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ポインターは正規化されません。ただし、算術演算が実行されると、正規化されます。したがって、h1234:1234であり、h1は正規化された1357: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インクリメントすると、オフセットが15を超えることができないため、0001:0000になります。つまり、正規化されます。

20
Vikas Verma

この回答のすべてのものは、古い8086および80286セグメント化メモリモデルにのみ関連しています。

近く:64kセグメントの任意のバイトをアドレス指定できる16ビットポインタ

far:セグメントとオフセットを含む32ビットポインタ。セグメントはオーバーラップする可能性があるため、2つの異なるfarポインタが同じアドレスを指す可能性があることに注意してください。

巨大:セグメントが「正規化」されている32ビットポインター。同じ値を持たない限り、2つの遠いポインターが同じアドレスを指すことはありません。

ティー:ジャムとパンの入った飲み物。

それは私たちをdohoh ohohに戻すでしょう

そして、これらのポインタが使用されるときは?

1980年代と90年代に32ビットWindowsが普及するまで、

3
JeremyP

一部のアーキテクチャでは、システム内のすべてのオブジェクトを指すことができるポインタは、有用なサブセットを指すことができるポインタよりも大きく、処理が遅くなります。多くの人が16ビットx86アーキテクチャに関連する回答をしました。さまざまなタイプのポインターが16ビットシステムで一般的でしたが、実装方法によっては、64ビットシステムでニア/フィアの区別が再び現れる可能性があります(多くの開発システムが64ビットポインターに移行しても驚かないでしょう多くの場合、それは非常に無駄になるという事実にもかかわらず、すべて。

多くのプログラムでは、メモリ使用量を2つのカテゴリに分類するのは非常に簡単です。合計するとかなり少量(64Kまたは4GB)になるが頻繁にアクセスされる小さなものと、合計するとはるかに大量になる可能性のある大きなものです。 、しかし、それほど頻繁にアクセスする必要はありません。アプリケーションが「大きなもの」領域のオブジェクトの一部を処理する必要がある場合、アプリケーションはその部分を「小さなもの」領域にコピーして処理し、必要に応じて書き戻します。

一部のプログラマーは、「近い」メモリと「遠い」メモリを区別する必要があることに不満を持っていますが、多くの場合、そのような区別を行うことで、コンパイラははるかに優れたコードを生成できます。

(注:多くの32ビットシステムでも、追加の命令なしでメモリの特定の領域に直接アクセスできますが、他の領域にはアクセスできません。たとえば、68000またはARMの場合、レジスタがグローバル変数ストレージを指すようにします。そのレジスタの最初の32K(68000)または2K(ARM)内の任意の変数を直接ロードすることが可能です。他の場所に格納されている変数をフェッチするには、アドレスを計算するための追加の命令が必要になります。より頻繁に使用される変数を優先領域に配置します。コンパイラに知らせることで、より効率的なコード生成が可能になります。

2
supercat

この用語は、16ビットアーキテクチャで使用されていました。

16ビットシステムでは、データは64Kbセグメントに分割されていました。ロード可能な各モジュール(プログラムファイル、動的にロードされたライブラリなど)には、最大64Kbのデータのみを格納できるデータセグメントが関連付けられていました。

NEARポインターは、16ビットストレージを備えたポインターであり、現在のモジュールデータセグメント内のデータ(のみ)を参照していました。

要件として64Kbを超えるデータを含む16ビットプログラムは、FARポインターを返す特別なアロケーターにアクセスできます。これは上位16ビットのデータセグメントIDであり、下位16ビットのそのデータセグメントへのポインターです。

さらに大規模なプログラムでは、64Kbを超える連続したデータを処理する必要があります。巨大なポインターは遠いポインターとまったく同じように見えます-32ビットのストレージがあります-しかし、アロケーターは、データセグメントセレクターをインクリメントするだけで次の64Kbのデータチャンクを到達しました。

基礎となるCおよびC++言語標準は、メモリモデルでこれらの概念を公式に認識していません。CまたはC++プログラムのすべてのポインタは同じサイズであると想定されています。したがって、NEAR、FAR、およびHUGE属性は、さまざまなコンパイラベンダーによって提供された拡張機能でした。

2
Chris Becke