考虑以下层次结构
struct A {
int a;
A() { f(0); }
A(int i) { f(i); }
virtual void f(int i) { cout << i; }
};
struct B1 : virtual A {
int b1;
B1(int i) : A(i) { f(i); }
virtual void f(int i) { cout << i+10; }
};
struct B2 : virtual A {
int b2;
B2(int i) : A(i) { f(i); }
virtual void f(int i) { cout << i+20; }
};
struct C : B1, virtual …Run Code Online (Sandbox Code Playgroud) c++ multiple-inheritance virtual-inheritance vtable memory-layout
包含一个或多个虚函数的每个类都有一个与之关联的Vtable.一个名为vptr的void指针指向该vtable.该类的每个对象都包含指向同一Vtable的vptr.那为什么vptr不是静态的呢?而不是将vptr与对象相关联,为什么不将它与类关联?

我的理解是,由于两个问题,虚函数可能会导致性能问题:vtable引起的额外derefencing以及编译器无法在多态代码中内联函数.
如果我将变量指针向下转换为其确切类型怎么办?那还有额外的费用吗?
class Base { virtual void foo() = 0; };
class Derived : public Base { void foo() { /* code */} };
int main() {
Base * pbase = new Derived();
pbase->foo(); // Can't inline this and have to go through vtable
Derived * pderived = dynamic_cast<Derived *>(pbase);
pderived->foo(); // Are there any costs due to the virtual method here?
}
Run Code Online (Sandbox Code Playgroud)
我的直觉告诉我,由于我将对象转换为其实际类型,编译器应该能够避免使用虚函数的缺点(例如,它应该能够内联方法调用,如果它想要).它是否正确?
在我转发后,编译器是否真的知道pderived是Derived类型的?在上面的例子中,看到pbase是Derived类型的微不足道,但在实际代码中它可能在编译时是未知的.
既然我已经写下来了,我想由于Derived类本身可以被另一个类继承,将pbase向下转换为Derived指针实际上并不能确保编译器的任何内容,因此它无法避免成本虚拟功能?
对于那些使用vtables的编译器实现:是否有任何在运行时更改虚函数表的情况?或者vtable仅在编译时填充,并且在运行时不执行任何操作来修改它们?
虚拟类的每个对象都有一个指向vtable的指针吗?
或者只有具有虚函数的基类对象具有它?
vtable存放在哪里?进程的代码部分或数据部分?
我有一个DLL one.dll,它使用TwoClass从two.dllvia 导出的类class __declspec(dllexport).我想one.dll用/delayload的two.dll,但我得到一个链接错误:
LINK : fatal error LNK1194: cannot delay-load 'two.dll' due to import
of data symbol '"__declspec(dllimport) const TwoClass::`vftable'"
(__imp_??_7TwoClass@@6B@)'; link without /DELAYLOAD:two.dll
Run Code Online (Sandbox Code Playgroud)
这是在发布版本中; 在Debug构建中它可以工作.(我不知道在vtable导出方面Release和Debug之间的区别是什么,我也找不到任何编译器开关或pragma来控制它.)
如何/delayload在发布版本中使用导出类似这样的类的DLL?
这个问题是一种消除未使用的虚函数的后续问题,对我的兴趣不够深入.
问题:在定义具有虚函数的类时,编译器为虚函数表分配存储,并存储指向表中函数的指针.这会导致链接器保留这些函数的代码,无论它们是否被调用.即使编译器优化设置要求消除死代码,这也可能导致大量死代码保留在可执行文件中.
现在,如果在可执行文件中没有任何地方存在特定虚函数的调用(或者换句话说,访问虚函数表的相应槽),则可以从虚函数表中省略相应的函数指针,并且链接器将删除函数的代码,可能会进一步遗漏其他未引用的代码.
显然,这不能由编译器完成,因为它只在链接时变得清楚是否调用了特定的虚函数(假设静态链接 - 很明显它不能用动态链接完成).我对链接器不够熟悉,以便判断编译器是否能够以链接器可以选择性地忽略表中各个未使用的条目的方式发出虚函数表.
基本上,我的思路是这样的:虚函数表中的函数指针是对函数的引用,链接器使用该函数来确定函数的代码需要保留在可执行文件中.以类似的方式,虚函数调用是对从其虚函数被调用的类派生的所有虚函数表中的特定槽的引用.这种引用是否可以通过这样一种方式传递给链接器:当它没有引用时,它可以忽略虚函数表槽?
请注意,当编译器可以在编译时确定调用目标时,这与使用直接调用替换虚函数调用不同.我知道一些编译器可以做到这一点,但这是一个不同的情况,因为函数实际上被调用,并且它是被删除的虚函数调度的开销.在我的情况下,我希望删除未调用的函数的整个代码.
如果我可以控制所有类定义,我可以手动删除所有未调用的虚函数.但是在使用库时这是不现实的.
这可以通过"链接时间优化"或"整个程序优化"来完成吗?是否有成功的编译器?
我想这是特定于实现的,但是对于使用libstdc ++和libc ++(gcc或clang)的armv7,arm64和x86_64构建,似乎vtable在开头总是有8个字节(64位为16)的填充vtable通常看起来像这样:
ldr.w r0, <address of vtable>
adds r0, 0x8
str r0, [r1] ; where r1 is the instance
Run Code Online (Sandbox Code Playgroud)
而vtable看起来像这样:
vtable+0x00: 0x00000000
vtable+0x04: 0x00000000
vtable+0x08: 0xfirstfunc
vtable+0x0c: 0xsecondfunc
vtable+0x10: 0xthirdfunc
Run Code Online (Sandbox Code Playgroud)
等等...
有谁知道这是为什么?
如果我有一个特质Foo,并且有一些实现者Bar,Baz.
impl Foo for Bar {
}
Run Code Online (Sandbox Code Playgroud)
impl Foo for Baz {
}
Run Code Online (Sandbox Code Playgroud)
但是假设我只使用其中一个作为特征对象,
let bar = Bar {..};
let foo: &dyn Foo = &bar;
Run Code Online (Sandbox Code Playgroud)
那么我的二进制文件仍然有两个 vtable 吗?这种行为在调试和发布版本之间是否会发生变化?
我对vptr和内存中对象的表示感到困惑,希望你能帮助我更好地理解这个问题.
考虑B从中继承A并定义虚函数f().从我了解到的记忆B类对象的表示是这样的:[ vptr | A | B ]
与vtbl该vptr指向包含B::f().我也明白,从铸造对象B来A什么都不做,除了忽略B在对象的端部.这是真的吗?这种行为不对吗?我们希望类型的对象A执行A::f()方法而不是B::f().
是否有一些vtables在系统中的类的数量?
一个将如何vtable类,由两个或多个类继承的是什么样子?如何将C的对象表示在内存中?
与问题3相同,但具有虚拟继承.