簡単な要約
(これも一番上に置きます):
(0) ポインタをアドレスとして考えることは、多くの場合、優れた学習ツールであり、多くの場合、通常のデータ型へのポインタの実際の実装です。
(1)しかし、多くの、おそらくほとんどのコンパイラでは、関数へのポインタはアドレスではありませんが、アドレスよりも大きい(通常は2倍、時にはそれ以上)か、実際にはメモリ内の構造体へのポインタであり、関数のアドレスや次のようなものが含まれています一定のプール。
(2) データ メンバへのポインタとメソッドへのポインタは、さらに見慣れないことがよくあります。
(3) FAR および NEAR ポインターの問題を伴うレガシー x86 コード
(4) セキュアな「ファット ポインタ」を備えたいくつかの例、特に IBM AS/400。
私はあなたがもっと見つけることができると確信しています。
詳細:
うーん!これまでの回答の多くは、かなり典型的な「プログラマーのウィニー」の回答ですが、コンパイラーのウィニーやハードウェアのウィニーではありません。私はハードウェアの弱虫のふりをしており、コンパイラの弱虫を扱うことが多いので、2 セントを投入させてください。
多くの、おそらくほとんどの C コンパイラでは、型のデータへのポインタT
は、実際にはT
.
罰金。
しかし、これらのコンパイラの多くでも、特定のポインタはアドレスではありません。を見ればわかりますsizeof(ThePointer)
。
たとえば、関数へのポインターは、通常のアドレスよりもかなり大きい場合があります。または、ある程度の間接性が含まれる場合もあります。 この記事は Intel Itanium プロセッサに関する 1 つの説明を提供しますが、私は他のものを見てきました。通常、関数を呼び出すには、関数コードのアドレスだけでなく、関数の定数プールのアドレスも知っている必要があります。これは、コンパイラが生成するのではなく、単一のロード命令で定数がロードされるメモリ領域です。複数の Load Immediate および Shift および OR 命令からの 64 ビット定数。したがって、1 つの 64 ビット アドレスではなく、2 つの 64 ビット アドレスが必要です。一部の ABI (アプリケーション バイナリ インターフェイス) はこれを 128 ビットとして移動しますが、関数ポインターは実際には前述の 2 つの実際のアドレスを含む関数記述子のアドレスである間接的なレベルを使用するものもあります。どちらが良いですか?あなたの視点に依存します: パフォーマンス、コードサイズ、いくつかの互換性の問題 - 多くの場合、コードはポインターが long または long long にキャストできると想定していますが、long long が正確に 64 ビットであると想定する場合もあります。このようなコードは標準に準拠していない可能性がありますが、それにもかかわらず、顧客はそれが機能することを望んでいる可能性があります。
私たちの多くは、NEAR POINTER と FAR POINTER を使用した、古い Intel x86 セグメント化アーキテクチャの痛ましい思い出を持っています。ありがたいことに、これらは今ではほぼ消滅しているので、簡単に要約すると: 16 ビット リアル モードでは、実際のリニア アドレスは
LinearAddress = SegmentRegister[SegNum].base << 4 + Offset
一方、保護モードでは、
LinearAddress = SegmentRegister[SegNum].base + offset
結果のアドレスは、セグメントに設定された制限に対してチェックされます。一部のプログラムは実際には標準の C/C++ FAR および NEAR ポインター宣言を使用していませんでしたが、多くのプログラムでは単に*T
--- と言われていましたが、コンパイラとリンカーの切り替えがあったため、たとえば、コード ポインターはポインターの近くにある可能性があり、コード内にあるものに対してわずか 32 ビット オフセットである可能性があります。 CS (コード セグメント) レジスタ。データ ポインターは FAR ポインターである場合があり、48 ビット値に対して 16 ビット セグメント番号と 32 ビット オフセットの両方を指定します。さて、これらの量はどちらも確かに住所に関連していますが、サイズが同じではないので、どちらが住所でしょうか? さらに、セグメントには、実際のアドレスに関連するものに加えて、読み取り専用、読み書き可能、実行可能などのアクセス許可も含まれていました。
もっと興味深い例は、私見ですが、IBM AS/400 ファミリーです (または、おそらくそうでした)。このコンピューターは、C++ で OS を実装した最初のコンピューターの 1 つです。このマシーンのポインターは、通常、実際のアドレス サイズの 2 倍でした。たとえば、このプレゼンテーションのようには 128 ビット ポインターと言っていますが、実際のアドレスは 48 ~ 64 ビットでした。また、追加情報、いわゆる機能があり、読み取り、書き込み、およびバッファー オーバーフローを防止するための制限などのアクセス許可を提供していました。はい。C/C++ と互換性を持ってこれを行うことができます。これが遍在していれば、中国の PLA とスラブ系マフィアはそれほど多くの西側のコンピューター システムにハッキングすることはないでしょう。しかし、歴史的にほとんどの C/C++ プログラミングは、パフォーマンスのセキュリティを無視してきました。最も興味深いのは、AS400 ファミリにより、オペレーティング システムが安全なポインタを作成できるようになったことです。これは、特権のないコードに与えることができますが、特権のないコードは偽造または改ざんできません。繰り返しますが、セキュリティと、標準に準拠している一方で、非常にずさんな非標準準拠の C/C++ コードは、このような安全なシステムでは機能しません。繰り返しますが、公式の基準があり、
ここで、私のセキュリティ ソープボックスから離れて、(さまざまな型の) ポインターが実際にはアドレスではないことが多い別の方法について説明します: データ メンバーへのポインター、メンバー関数メソッドへのポインター、およびそれらの静的バージョンは、普通の住所。この投稿 が言うように:
これを解決する方法はたくさんあります [単一の継承と複数の継承、および仮想継承に関連する問題]。Visual Studio コンパイラがそれを処理する方法を次に示します: 多重継承されたクラスのメンバー関数へのポインターは、実際には構造体です。」そして、「関数ポインターをキャストすると、そのサイズが変わる可能性があります!」と続けます。
おそらく、私のセキュリティに関する (中の) 正当化から推測できるように、私は C/C++ ハードウェア/ソフトウェア プロジェクトに関与してきました。このプロジェクトでは、ポインターは生のアドレスよりも機能のように扱われてきました。
続けることもできますが、理解していただければ幸いです。
簡単な要約
(これも一番上に置きます):
(0) ポインターをアドレスと考えるのは、多くの場合、優れた学習ツールであり、多くの場合、通常のデータ型へのポインターの実際の実装です。
(1)しかし、多くの、おそらくほとんどのコンパイラでは、関数へのポインタはアドレスではありませんが、アドレスよりも大きい(通常は2X、時にはそれ以上)か、実際にはメモリ内の構造体へのポインタであり、関数のアドレスや次のようなものが含まれています一定のプール。
(2) データ メンバへのポインタとメソッドへのポインタは、さらに見慣れないことがよくあります。
(3) FAR および NEAR ポインターの問題を伴うレガシー x86 コード
(4) セキュアな「ファット ポインタ」を備えたいくつかの例、特に IBM AS/400。
私はあなたがもっと見つけることができると確信しています。