5

この質問は、特に移植性のない MSVC ABI に関するものです。

私は、C++ に相当するものをtypeid、明らかに非移植性でありながら魔法ではない C++ で書こうとしています。Itanium ABI (Linux/Mac で使用) の場合は、非常に簡単です。

const std::type_info& dynamicast_typeid(void *mdo)
{
    std::type_info **vptr = *reinterpret_cast<std::type_info ***>(mdo);
    std::type_info *typeinfo_ptr = vptr[-1];
    return *typeinfo_ptr;
}

だから今、私は 64 ビットの MSVC ABI を見ていて、それをぶつけて、困惑しています。非常に単純なクラス (オフセット 0 の vfptr で始まるクラス) の場合、Itanium とほぼ同じくらい簡単です。

const std::type_info& dynamicast_typeid(void *mdo)
{
    int *rtti_complete_object_locator = ((int ***)mdo)[0][-1];
    char *result = (char *) rtti_complete_object_locator;
    result -= rtti_complete_object_locator[5];
    result += rtti_complete_object_locator[3];
    return *(std::type_info*)result;
}

(このコードは、Wine プロジェクトの__RTtypeidに基づいています。)

問題は、一部の C++ クラスがオフセット 0 の vfptr で始まらないことです! vbptr で始まることもあります。

struct Class1 { virtual ~Class1() {} };
struct Class2 : virtual Class1 {};

Class1vfptr で始まります。Class2vbptr で始まります。

クラスが vbptr で始まる場合、その最初の仮想ベース サブオブジェクト (レイアウト順で最初の、したがって「最もリーフ」) のオフセット 0 に常に vfptr があると私は信じてます vbptr で始まるクラスを扱うため、代わりに次のようにします。

const std::type_info& dynamicast_typeid_for_vbptr_class(void *mdo)
{
    int first_vbase_offset = ((int**)mdo)[0][1];
    mdo = (char*)mdo + first_vbase_offset;
    int *rtti_complete_object_locator = ((int ***)mdo)[0][-1];
    char *result = (char *) rtti_complete_object_locator;
    result -= rtti_complete_object_locator[5];
    result += rtti_complete_object_locator[3];
    return *(std::type_info*)result;
}

MSVCが実際に同等のものを生成すると判断しました

    if constexpr(IS_VBPTR_CLASS) {
        int first_vbase_offset = ((int**)mdo)[0][1];
        mdo = (char*)mdo + first_vbase_offset;
    }
    return __RTtypeid(mdo);

C++ 式をコンパイルするときtypeid(x)— これは、「コンパイラは、静的な型と、コンパイラがすべての型のレイアウトを知っているという事実に基づいて、vbptr があるIS_VBPTR_CLASSかどうかを魔法のように知る」ための擬似コードです。xx

ただし、私の場合、 の静的型がわかりません。わかったとしても、(C++ 内から、テンプレート メタプログラミングを使用して) vbptr で始まるxかどうかを調べる方法がわかりません。x

最後に、私は先に進み、少しごまかしました

const std::type_info& dynamicast_typeid(void *mdo)
{
    while (((int**)mdo)[0][0] == 0) {
        mdo = (char *)mdo + ((int**)mdo)[0][1];
    }
    int *rtti_complete_object_locator = ((int ***)mdo)[0][-1];
    char *result = (char *)complete_object_locator;
    result -= rtti_complete_object_locator[5];
    result += rtti_complete_object_locator[3];
    return *(std::type_info*)result;
}

"Class1 in Class2" の vftable に格納されている typeinfo がClass1、最も派生した型ではなく、サブオブジェクト type の typeinfo を保持していることを発見するだけClass2です。つまり、パズルのピースがもう 1 つ欠けています。

私の質問を一言で言え(void*)&object_of_type_class2:typeid(Class2)

4

1 に答える 1

1

私は今働くものを持っています!

この関数dynamicast_to_mdoは、最も派生したオブジェクトを指すようdynamic_cast<void*>(p)に調整します。p

この関数dynamicast_typeidは、の vtabletypeid(p)から typeinfo を取得します。p私は質問で提供したファッジ/ハックを使用しているだけで、数時間前になぜ間違った回答を得ていたのか実際にはわかりません。間違った typeinfo が表示されたのはtypeid、破棄されたスタック変数へのダングリング リファレンスを誤って取得しようとしたことが原因だった可能性があります。

// 64-bit MSVC ABI
void *dynamicast_to_mdo(void *p)
{
    if (((int**)p)[0][0] == 0) {
        p = (char *)p + ((int**)p)[0][1];
    }
    int *complete_object_locator = ((int ***)p)[0][-1];
    int mdoffset = complete_object_locator[1];
    void *adjusted_this = static_cast<char *>(p) - mdoffset;
    return adjusted_this;
}

// 64-bit MSVC ABI
const std::type_info& dynamicast_typeid(void *p)
{
    if (((int**)p)[0][0] == 0) {
        p = (char *)p + ((int**)p)[0][1];
    }
    int *complete_object_locator = ((int ***)p)[0][-1];

    char *result = (char *)complete_object_locator;
    result -= complete_object_locator[5];
    result += complete_object_locator[3];
    return *(const std::type_info*)result;
}
于 2017-06-18T22:44:15.633 に答える