避免多态类中的虚拟表

时间:2015-02-16 04:38:10

标签: c++ visual-studio polymorphism

根据this page,Microsoft的扩展属性__declspec(novtable)"阻止编译器生成代码以初始化类的构造函数和析构函数中的vfptr ...这种形式的__declspec可以显着减少代码大小。"

我使用Visual Studio 2013更新4,发布配置,x64编译了以下代码,并获得了之后显示的汇编代码。

struct __declspec(novtable) textEmpty
{
    virtual void fs() = 0;
};

struct textEmpty2
{
    virtual void fs() = 0;
};

struct Y : textEmpty
{
    void fs() override;
};

void Y::fs()
{
    wcout << sizeof( * this ) << endl;
}

struct Y2 : textEmpty2
{
    void fs() override;
};

void Y2::fs()
{
    wcout << sizeof( * this ) << endl;
}

int main()
{
    Y * d_ = new Y;
    Y2 * d_2 = new Y2;
    d_->fs();
    d_2->fs();
    return 0;
}
    Y * d_ = new Y;
mov         ecx,8
call        operator new (07FF7AEED1090h)
test        rax,rax
je          main+26h (07FF7AEEA2A66h)
lea         rdx,[Y::`vftable' (07FF7AEF189B0h)]
mov         qword ptr [rax],rdx
    Y2 * d_2 = new Y2;
mov         ecx,8
call        operator new (07FF7AEED1090h)
lea         rcx,[Y2::`vftable' (07FF7AEF189C0h)]
test        rax,rax
cmove       rcx,qword ptr [rax]
mov         qword ptr [rax],rcx

问题1 。我在两个构造函数中计算相同数量的指令。鉴于微软关于__declspec(novtable)减少代码大小的说法,我错过了哪些内容?

问题2 。在汇编代码Y2 * d_2 = new Y2;中,第三行修改RCX,第五行也修改。 RCX未在第四行中使用。我缺少副作用吗?


UPDATE 编译标志位于下方,是,/ O2已设置。此外,我尝试禁用语言扩展并启用它。结果是一样的。

  

/ GS / GL / W3 / Gy / Zc:wchar_t / Zi / Gm- / O2 / sdl /Fd"x64\Release\vc120.pdb" / fp:exact / D&#34; WIN32&#34; / D&#34; NDEBUG&#34; / D&#34; _CONSOLE&#34; / D&#34; _LIB&#34; / D&#34; _UNICODE&#34; / D&#34; UNICODE&#34; / errorReport:prompt / WX- / Zc:forScope / Gd / Oi / MT / Fa&#34; x64 \ Release \&#34; / EHsc / nologo / Za / Fo&#34; x64 \ Release \&#34;

1 个答案:

答案 0 :(得分:2)

好问题。我会试一试......

问题#1:

__ declspec(novtable)确实减少了代码大小,但仅适用于具有该属性的类,如文档所指定的那样:

  

在许多情况下,这会删除对vtable的唯一引用   与相关联,因此链接器将删除它。运用   这种形式的__declspec可以导致代码的显着减少   大小

这意味着您不会在子类中看到此效果。我已将代码修改为:

struct __declspec(novtable) textEmpty {
    virtual void fs() {};
};
struct textEmpty2 {
    virtual void fs() {};
};
struct Y : textEmpty {
    void fs() override;
};
void Y::fs() {
    wcout << sizeof(*this) << endl;
}
struct Y2 : textEmpty2 {
    void fs() override;
};
void Y2::fs()     {
    wcout << sizeof(*this) << endl;
}
int main() {
    textEmpty* e = new textEmpty;
    textEmpty2* e2 = new textEmpty2;
    Y * d_ = new Y;
    Y2 * d_2 = new Y2;
    d_->fs();
    d_2->fs();
    return 0;
}

汇编语言输出为:

    textEmpty* e = new textEmpty;
000000013FFB12BA  mov         ecx,8  
000000013FFB12BF  call        qword ptr [__imp_operator new (013FFB3178h)]  
    textEmpty2* e2 = new textEmpty2;
000000013FFB12C5  mov         ecx,8  
000000013FFB12CA  call        qword ptr [__imp_operator new (013FFB3178h)]  
000000013FFB12D0  test        rax,rax  
000000013FFB12D3  je          main+2Fh (013FFB12DFh)  
000000013FFB12D5  lea         rcx,[textEmpty2::`vftable' (013FFB3348h)]  
000000013FFB12DC  mov         qword ptr [rax],rcx  
    Y * d_ = new Y;
000000013FFB12DF  mov         ecx,8  
000000013FFB12E4  call        qword ptr [__imp_operator new (013FFB3178h)]  
000000013FFB12EA  mov         rdi,rax  
000000013FFB12ED  test        rax,rax  
000000013FFB12F0  je          main+4Eh (013FFB12FEh)  
000000013FFB12F2  lea         rax,[Y::`vftable' (013FFB32F0h)]  
000000013FFB12F9  mov         qword ptr [rdi],rax  
000000013FFB12FC  jmp         main+50h (013FFB1300h)  
000000013FFB12FE  xor         edi,edi  
    Y2 * d_2 = new Y2;
000000013FFB1300  mov         ecx,8  
000000013FFB1305  call        qword ptr [__imp_operator new (013FFB3178h)]  
000000013FFB130B  mov         rbx,rax  
000000013FFB130E  test        rax,rax  
000000013FFB1311  je          main+6Fh (013FFB131Fh)  
000000013FFB1313  lea         rax,[Y2::`vftable' (013FFB3300h)]  
000000013FFB131A  mov         qword ptr [rbx],rax  
000000013FFB131D  jmp         main+71h (013FFB1321h)  
000000013FFB131F  xor         ebx,ebx  

现在有意义吗?当在具有novtable的类(即textEmpty)上调用new时,编译器不会生成vftable指针初始化代码。另一方面,其他三个没有novtable属性的类的新语句会生成vftable指针初始化代码。

问题#2:

出于某种原因,我的编译器输出了不同的东西。这是我的旗帜:

  

/ GS / GL / W3 / Gy / Zc:wchar_t / Zi / Gm- / O2 /Fd"x64\Release\vc120.pdb“   / fp:exact / D“WIN32”/ D“NDEBUG”/ D“_CONSOLE”/ D“_LIB”/ D   “_UNICODE”/ D“UNICODE”/ errorReport:prompt / WX- / Zc:forScope / Gd / Oi   / MD / Fa“x64 \ Release \”/ EHsc / nologo / Fo“x64 \ Release \”   /Fp"x64\Release\sotestaaa.pch“

我的输出没有像输出那样的测试和cmove行:

test        rax,rax
cmove       rcx,qword ptr [rax]

但这些行基本上转化为

 if (rax == 0) mov rcx, [rax]
如果你问我这真的很愚蠢。如果rax == 0(即new返回0),那些行将导致空指针异常。如果rax不为0,则代码不执行任何操作。

同样,我的编译器VS 2013(12.0.21005.1 REL)不会生成该代码。

另请注意我的输出是明智的。当novtable存在时,它只是做一个新的,没有别的。当novtable不存在时,它会有一个新的。如果new的结果不为null,则它将vftable的地址设置为正确的内存位置(由new返回)。

另请注意,因为在c ++代码中,我们在d和d_2之后立即调用fs(),编译器足够聪明,可以将指向d和d_2的指针保存到临时寄存器中以供以后使用:

保存d:

  mov         rdi,rax

保存d_2:

  mov         rbx,rax