1

次のマクロが何をしているのかわかりませんか? 誰かがそれを見るのを手伝ってくれるなら、それはありがたいです。

#define BASE_OFFSET(ClassName,BaseName)\
(DWORD(static_cast < BaseName* >( reinterpret_cast\
< ClassName* >(Ox10000000)))-Ox10000000)

それがどこから来ているのか知りたい人は、Don Box Book Essential COM の第 3 章から出てきます。彼はインターフェイス テーブルを使用して QueryInterface 関数を構築しており、上記のマクロはインターフェイス vtable へのポインターを見つけるために何らかの形で使用されています。 class は BaseName を実装する ClassName ですが、それがどのように行われているかはわかりません。

4

2 に答える 2

7

コンパイラに次のように伝えます:「0x10000000 にあるClassNameオブジェクトを想像してください。BaseNameデータは、0x10000000 に対して、そのオブジェクトのどこから始まるでしょうか?」

複数のベースを持つクラス オブジェクトのメモリ レイアウトを考えてみましょう。

class A: B, C{};

A オブジェクトを構成するメモリ ブロックには、B に属するデータのチャンク、C に属するデータのチャンク、および A に固有のデータがあります。少なくとも 1 つのベースのデータのアドレスを指定することはできません。クラス インスタンス全体のアドレスと同じように、さまざまなメソッドに渡すポインターの数値thisを変える必要があります。マクロは差の値を取得します。

編集: vtable へのポインターは、慣例により、仮想関数を持つクラスの最初のデータ メンバーです。したがって、ベースデータのアドレスを見つけることによって、その vtable ポインターのアドレスが見つかります。

さて、型変換について。通常、ポインターを型キャストする場合、操作は内部的に簡単です。アドレスの数値は、それが指している型に依存しません。データ型の概念そのものが C レベルにのみ存在します。ただし、重要な例外が 1 つあります。多重継承でオブジェクト ポインターをキャストする場合です。先ほど説明したようthisに、基本クラスのメソッドに渡す必要があるポインターは、派生オブジェクトのものと数値的に異なる場合があります。

したがって、 static_cast と reinterpret_cast の違いは、この違いをうまく捉えています。reinterpret_cast を使用すると、コンパイラに次のように伝えます。これは、型システムの意図的な転覆であり、危険ですが、場合によっては必要になります。この種のキャストは、定義上、些細なことです-あなたがそう言うからです。

「些細な」とは、つまり、ポインターの数値は変化しません。

static_cast は、より高レベルの構造です。この特定のケースでは、オブジェクトとそのベースの間でキャストしています。これは、C++ クラス規則の下では合理的で安全なキャストですが、数値的には自明ではない可能性があります。そのため、マクロは 2 つの異なる型キャストを使用します。static_cast は型システムに違反していません。

要点をまとめると:

reinterpret_cast<ClassName* >(OxlOOOOOOO)

安全でない操作です。偽のオブジェクトへの偽のポインターを返しますが、逆参照しないので問題ありません。

static_cast<BaseName*>(...)

は安全な操作です (危険なポインタ、皮肉を伴います)。重要なポインターの型キャストが発生する部分です。

(DWORD(...)-OxlOOOOOOO)

は純粋な算術です。ここで、危険性が倍増します。ポインターをポインターとして使用するのではなく、整数にキャストし直して、ポインターであったことを忘れてしまいます。

最後の段階は、次のように言い換えることができます。

((char*)(...)-(char*)OxlOOOOOOO)

それがより理にかなっている場合。

于 2012-07-24T19:38:53.673 に答える