MSVC 2012 为不同的文件生成不同的 vtable 指针偏移量

MSVC 2012 generates different vtable pointer offsets for different files

假设我有 X64 版本配置 这是一个混淆的代码片段...

// Hdr1.h
// Dozen of includes

class Cls1
{
public:
    Cls1();
    virtual void bar();
// ...
protected:
// about 7 fields where some of them are of complex template type.
bool isFlag1 : 1;
bool isFlag2 : 1;
};


// Hdr2
// Dozens of includes
class Cls2
{
public:
    // ...
    void foo();
};

我有单独的翻译单元来实现这些 classes。从 foo 说,我尝试访问 Cls1::bar 的虚拟方法,但出现崩溃(访问冲突)。

void Cls2::foo()
{
   //...
   Cls1 * pCls1 = // somehow I get this goddamn pointer

   pCls1->bar(); // Here I crash
}

从反汇编中我看到 Cls1::Cls1 将 vtable ptr 放在偏移量 8 的最开头。从 Cls2::foo 的反汇编中我看到它从偏移量零开始指向 vtable 的指针。调试器也无法正确看到这个 vtable。如果我在偏移量 8 处手动获取 vtable - 地址在此 table.

中似乎是正确的

问题是 - 为什么会发生这种情况,什么 pragma 会导致这种情况或其他情况?两个翻译单元的编译标志相同。

下面我补充一点反汇编: 这是我在代码中遇到的正常情况:

 Module1!CSomeOkClass::CreateObjInstance:
 sub     rsp,28h
 mov     edx,4                                  ; own inlined operator new
 lea     ecx,[rdx+34h]                          ; own inlined operator new
 call    OwnMemoryRoutines!OwnMalloc (someAddr) ; own inlined operator new
 xor     edx,edx
 test    rax,rax
 je      Module1!CSomeOkClass::CreateObjInstance+0x40 (someAddr)
 **lea     rcx,[Module1!CSomeOkClass::`vftable' (someAddr)] ; Inlined CSomeOkClass::CSomeOkClass < vtable ptr**
 mov     qword ptr [rax+8],rdx                  ; Inlined CSomeOkClass::CSomeOkClass
 mov     qword ptr [rax+10h],rdx                    ; Inlined CSomeOkClass::CSomeOkClass
 mov     qword ptr [rax+18h],rdx                    ; Inlined CSomeOkClass::CSomeOkClass
 mov     byte ptr [rax+20h],dl                  ; Inlined CSomeOkClass::CSomeOkClass
 mov     qword ptr [rax+28h],rdx                    ; Inlined CSomeOkClass::CSomeOkClass
 **mov     qword ptr [rax],rcx                  ; Inlined CSomeOkClass::CSomeOkClass < offset zero**

现在让我们看看我得到了什么 Cls1::Cls1:

 Module1!Cls1::Cls1:
 mov     qword ptr [rsp+8],rbx
 push    rdi
 sub     rsp,20h
 **lea     rax,[Module1!Cls1::`vftable' (someAddress)]  ; vtable address**
 mov     rbx,rdx
 mov     rdi,rcx
 **mov     qword ptr [rcx+8],rax                ; Places at offset 8**

我向您保证,Cls2 期望指向 vtable 的指针位于偏移量零处。

编译选项为: /nologo /WX /W3 /MD /c /Zc:wchar_t /Zc:forScope /Zm192 /bigobj /d2Zi+ /Zi /Oi /GS- /GF /Oy- /fp:fast /Gm- /Ox / Gy /Ob2 /GR- /Os

我注意到 Cls1::Cls1 大量使用内联的 SSE 指令。

编译版本: Microsoft (R) C/C++ 针对 x64 优化编译器版本 17.00.50727.1

请注意此代码在不同 platforms/compilers.

上工作正常

我设法弄清楚问题实际上出在我在 Cl1 定义的最后这个位域上。如果我使 isFlag1 + isFlag2 成为普通布尔值,则生成的 ctor 会将指向 vtable 的指针置于偏移量零处。这些标志在 ctor 的初始化列表中初始化。通过逐行注释掉 class 的代码,我将问题缩小到这个位域。为了对此进行调查,我使用了 WinDbg,/P 编译器选项,使用提供的原始标志 + /FAs /Fa 手动编译了 cpp 单元。看来这是编译器的错误。

我设法弄清楚问题实际上出在我在 Cl1 定义的最后这个位域上。如果我使 isFlag1 + isFlag2 成为普通布尔值,则生成的 ctor 会将指向 vtable 的指针置于偏移量零处。这些标志在 ctor 的初始化列表中初始化。通过逐行注释掉 class 的代码,我将问题缩小到这个位域。为了对此进行调查,我使用了 WinDbg,/P 编译器选项,使用提供的原始标志 + /FAs /Fa 手动编译了 cpp 单元。看来这是编译器的错误。