gpt4 book ai didi

c++ - 虚方法和虚表查找的最终说明符

转载 作者:太空狗 更新时间:2023-10-29 20:24:16 26 4
gpt4 key购买 nike

我观察到,当某个类的方法在 C++11 中被标记为 final 时,vtable 中没有查找来调用该方法,即使是从指针,至少是GCC 生产的组件。以这段代码为例:

class Base {
public:
Base() : retval(0) {}
virtual ~Base(){}
virtual int method() {
return retval;
}
protected:
uint32_t retval;
};

class DerivedFinal : public Base {
public:
int method() final {
return retval + 2;
}
};

int main() {
Base *bptr = new Base();
DerivedFinal *df = static_cast<DerivedFinal *>(bptr);
return df->method();
}

请注意,代码使用这样的返回值以使汇编代码易于阅读。

main 的程序集如下所示:

<+0>:     push   %rbp
<+1>: mov %rsp,%rbp
<+4>: push %rbx
<+5>: sub $0x18,%rsp
<+9>: mov $0x10,%edi
<+14>: callq 0x400750 <_Znwm@plt>
<+19>: mov %rax,%rbx
<+22>: mov %rbx,%rdi
<+25>: callq 0x400900 <_ZN4BaseC2Ev>
<+30>: mov %rbx,-0x18(%rbp)
<+34>: mov -0x18(%rbp),%rax
<+38>: mov %rax,-0x20(%rbp)
<+42>: mov -0x20(%rbp),%rax
<+46>: mov %rax,%rdi
<+49>: callq 0x400986 <_ZN12DerivedFinal6methodEv> // This is the method call
<+54>: add $0x18,%rsp
<+58>: pop %rbx
<+59>: pop %rbp
<+60>: retq

可以看出,调用该方法时没有进行任何 vtable 查找(如果该方法未标记为 final,则不会发生这种情况)。即使有继承自 DerivedFinal 的类,代码的行为方式也相同 我的问题是......这是标准行为吗?


编辑:让我们以不是未定义行为的方式重写代码,以明确显示方法是如何跳过 vtable 的,而在不是时查找它:

class Base {
public:
Base() : retval(0) {}
virtual ~Base(){}
virtual int method() {
return retval;
}
protected:
uint32_t retval;
};

class DerivedFinal : public Base {
public:
int method() final {
return retval + 2;
}
};

class DerivedNotFinal : public Base {
public:
int method() {
return retval + 3;
}
};

int main() {
DerivedFinal *df = new DerivedFinal();
DerivedNotFinal *dnf = new DerivedNotFinal();
int res_final = df->method();
int res_not_final = dnf->method();
return 0;
}

和程序集转储:

<+0>:     push   %rbp
<+1>: mov %rsp,%rbp
<+4>: push %rbx
<+5>: sub $0x28,%rsp
<+9>: mov $0x10,%edi
<+14>: callq 0x4007b0 <_Znwm@plt>
<+19>: mov %rax,%rbx
<+22>: movq $0x0,(%rbx)
<+29>: movl $0x0,0x8(%rbx)
<+36>: mov %rbx,%rdi
<+39>: callq 0x400a5c <_ZN12DerivedFinalC2Ev> // First ctor...
<+44>: mov %rbx,-0x18(%rbp)
<+48>: mov $0x10,%edi
<+53>: callq 0x4007b0 <_Znwm@plt>
<+58>: mov %rax,%rbx
<+61>: movq $0x0,(%rbx)
<+68>: movl $0x0,0x8(%rbx)
<+75>: mov %rbx,%rdi
<+78>: callq 0x400a82 <_ZN15DerivedNotFinalC2Ev> // Second ctor...
<+83>: mov %rbx,-0x20(%rbp)
<+87>: mov -0x18(%rbp),%rax
<+91>: mov %rax,%rdi
<+94>: callq 0x400a34 <_ZN12DerivedFinal6methodEv> // Call to DerivedFinal::method directly
<+99>: mov %eax,-0x24(%rbp) // Save result in stack
<+102>: mov -0x20(%rbp),%rax
<+106>: mov (%rax),%rax
<+109>: add $0x10,%rax
<+113>: mov (%rax),%rax
<+116>: mov -0x20(%rbp),%rdx
<+120>: mov %rdx,%rdi
<+123>: callq *%rax // Call to DerivedNotFinal::method via vtable (indirect call)
<+125>: mov %eax,-0x28(%rbp) // Save result in stack
<+128>: mov $0x0,%eax
<+133>: add $0x28,%rsp
<+137>: pop %rbx
<+138>: pop %rbp
<+139>: retq

通过这个例子,行为也很清楚。对 DerivedFinal::method 的调用不需要任何 vtable 查找,而对 DerivedNotFinal::method 的调用需要间接访问。在我看来,这种行为(带有 final 关键字)在某些性能关键的应用程序中是需要的,这就是为什么我问这种行为是否是标准的。

最佳答案

您的代码通过 static_cast 将指向不是 DerivedFinal 的对象的指针转换为 DerivedFinal * 来调用未定义的行为。

编译器有权做任何事情,包括让你的猫怀孕或召唤鼻恶魔。

在一个更明智的例子中,标记为 final 的方法不能在进一步的派生类中被覆盖,因此编译器可能 - 一个好的优化编译器应该 - 将调用去虚拟化。

关于c++ - 虚方法和虚表查找的最终说明符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28854132/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com