避免多态中的虚拟表 类
Avoiding virtual tables in polymorphic classes
根据this page,微软的扩展属性__declspec(novtable)
"stops the compiler from generating code to initialize the vfptr in the constructor(s) and destructor of the class … Using this form of __declspec can result in a significant reduction in code size."
我用 Visual Studio 2013 update 4, release configuration, 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。我计算了两个构造函数中相同数量的指令。鉴于 Microsoft 关于 __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:precise /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_LIB" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oi /MT /Fa"x64\Release\" /EHsc /nologo /Za /Fo"x64\Release\"
好问题。我会试一试...
问题 #1:
__declspec(novtable) 确实减少了代码大小,但仅针对具有该属性的 class,如文档所述:
In many cases, this removes the only references to the vtable that are
associated with the class and, thus, the linker will remove it. Using
this form of __declspec can result in a significant reduction in code
size.
这意味着您不会在 child class 中看到此效果。我已将您的代码修改为:
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)在 class 上调用 new 时,编译器不会生成 vftable 指针初始化代码。另一方面,其他三个 class 没有 novtable 属性的新语句生成 vftable 指针初始化代码。
问题 #2:
出于某种原因,我的编译器输出了一些不同的东西。这是我的旗帜:
/GS /GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /Fd"x64\Release\vc120.pdb"
/fp:precise /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"
我的输出没有像你的输出那样的 test 和 cmove 行:
test rax,rax
cmove rcx,qword ptr [rax]
但这些行基本上转化为
if (rax == 0) mov rcx, [rax]
如果你问我这真的很愚蠢。如果 rax == 0(即 new returns 0),这些行将导致空指针异常。如果 rax 不为 0,则代码不执行任何操作。
同样,我的编译器 VS 2013 (12.0.21005.1 REL) 不会生成该代码。
另请注意,我的输出是合理的。当有 novtable 时,它只是做一个新的,没有别的。当 novtable 不存在时,它会做一个新的。如果 new 的结果不为空,则它将 vftable 的地址设置为正确的内存位置(由 new 返回)。
还要注意,因为在 c++ 代码中,我们在 new 之后立即对 d 和 d_2 调用 fs(),编译器足够聪明,可以将指向 d 和 d_2 的指针保存到稍后使用的临时寄存器:
节省天数:
mov rdi,rax
节省 d_2:
mov rbx,rax
根据this page,微软的扩展属性__declspec(novtable)
"stops the compiler from generating code to initialize the vfptr in the constructor(s) and destructor of the class … Using this form of __declspec can result in a significant reduction in code size."
我用 Visual Studio 2013 update 4, release configuration, 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。我计算了两个构造函数中相同数量的指令。鉴于 Microsoft 关于 __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:precise /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_LIB" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oi /MT /Fa"x64\Release\" /EHsc /nologo /Za /Fo"x64\Release\"
好问题。我会试一试...
问题 #1:
__declspec(novtable) 确实减少了代码大小,但仅针对具有该属性的 class,如文档所述:
In many cases, this removes the only references to the vtable that are associated with the class and, thus, the linker will remove it. Using this form of __declspec can result in a significant reduction in code size.
这意味着您不会在 child class 中看到此效果。我已将您的代码修改为:
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)在 class 上调用 new 时,编译器不会生成 vftable 指针初始化代码。另一方面,其他三个 class 没有 novtable 属性的新语句生成 vftable 指针初始化代码。
问题 #2:
出于某种原因,我的编译器输出了一些不同的东西。这是我的旗帜:
/GS /GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /Fd"x64\Release\vc120.pdb" /fp:precise /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"
我的输出没有像你的输出那样的 test 和 cmove 行:
test rax,rax
cmove rcx,qword ptr [rax]
但这些行基本上转化为
if (rax == 0) mov rcx, [rax]
如果你问我这真的很愚蠢。如果 rax == 0(即 new returns 0),这些行将导致空指针异常。如果 rax 不为 0,则代码不执行任何操作。
同样,我的编译器 VS 2013 (12.0.21005.1 REL) 不会生成该代码。
另请注意,我的输出是合理的。当有 novtable 时,它只是做一个新的,没有别的。当 novtable 不存在时,它会做一个新的。如果 new 的结果不为空,则它将 vftable 的地址设置为正确的内存位置(由 new 返回)。
还要注意,因为在 c++ 代码中,我们在 new 之后立即对 d 和 d_2 调用 fs(),编译器足够聪明,可以将指向 d 和 d_2 的指针保存到稍后使用的临时寄存器:
节省天数:
mov rdi,rax
节省 d_2:
mov rbx,rax