C++虚拟方法的对象大小
我对虚拟对象的大小有一些疑问。
1) 虚函数
class A {
public:
int a;
virtual void v();
}
A类的大小为8字节....一个整数(4字节)加1个虚指针(4字节) 天气晴朗!
class B: public A{
public:
int b;
virtual void w();
}
B级的尺寸是多少?我使用 sizeof B 进行了测试,它打印 12
B类和A类都有虚函数,是否意味着只有一个vptr?为什么只有一个vptr?
class A {
public:
int a;
virtual void v();
};
class B {
public:
int b;
virtual void w();
};
class C : public A, public B {
public:
int c;
virtual void x();
};
C的sizeof是20......
看起来在这种情况下,布局中有两个vptr......这是怎么发生的?我认为两个 vptr 一个用于 A 类,另一个用于 B 类......那么 C 类的虚函数没有 vptr 吗?
我的问题是,继承中vptr的数量有什么规则?
2) 虚拟继承
class A {
public:
int a;
virtual void v();
};
class B: virtual public A{ //virtual inheritance
public:
int b;
virtual void w();
};
class C : public A { //non-virtual inheritance
public:
int c;
virtual void x();
};
class D: public B, public C {
public:
int d;
virtual void y();
};
A 的大小为 8 个字节 -------------- 4(int a) + 4 (vptr) = 8
B 的大小为 16 个字节 - ------------- 没有 virtual 应该是 4 + 4 + 4 = 12。为什么这里还有 4 个字节? B类的布局是怎样的?
C的大小是12字节。 -------------- 4 + 4 + 4 = 12. 很清楚!
D 的大小是 32 字节 -------------- 应该是 16(class B) + 12(class C) + 4(int d) = 32,对吗?
class A {
public:
int a;
virtual void v();
};
class B: virtual public A{ //virtual inheritance here
public:
int b;
virtual void w();
};
class C : virtual public A { //virtual inheritance here
public:
int c;
virtual void x();
};
class D: public B, public C {
public:
int d;
virtual void y();
};
sizeof A is 8
sizeof B is 16
sizeof C is 16
sizeof D is 28 这是否意味着 28 = 16(class B) + 16(class C) - 8(class A) + 4 (这是什么?)
我的问题是,为什么应用虚拟继承时有多余的空间吗?
在这种情况下,对象大小的基本规则是什么?
当 virtual 应用于所有基类和部分基类时,有什么区别?
I have some questions about the object size with virtual.
1) virtual function
class A {
public:
int a;
virtual void v();
}
The size of class A is 8bytes....one integer(4 bytes) plus one virtual pointer(4 bytes)
It's clear!
class B: public A{
public:
int b;
virtual void w();
}
What's the size of class B? I tested using sizeof B, it prints
12
Does it mean that only one vptr is there even both of class B and class A have virtual function? Why there is only one vptr?
class A {
public:
int a;
virtual void v();
};
class B {
public:
int b;
virtual void w();
};
class C : public A, public B {
public:
int c;
virtual void x();
};
The sizeof C is 20........
It seems that in this case, two vptrs are in the layout.....How does this happen? I think the two vptrs one is for class A and another is for class B....so there is no vptr for the virtual function of class C?
My question is, what's the rule about the number of vptrs in inheritance?
2) virtual inheritance
class A {
public:
int a;
virtual void v();
};
class B: virtual public A{ //virtual inheritance
public:
int b;
virtual void w();
};
class C : public A { //non-virtual inheritance
public:
int c;
virtual void x();
};
class D: public B, public C {
public:
int d;
virtual void y();
};
The sizeof A is 8 bytes -------------- 4(int a) + 4 (vptr) = 8
The sizeof B is 16 bytes -------------- Without virtual it should be 4 + 4 + 4 = 12. why there is another 4 bytes here? What's the layout of class B ?
The sizeof C is 12 bytes. -------------- 4 + 4 + 4 = 12. It's clear!
The sizeof D is 32 bytes -------------- it should be 16(class B) + 12(class C) + 4(int d) = 32. Is that right?
class A {
public:
int a;
virtual void v();
};
class B: virtual public A{ //virtual inheritance here
public:
int b;
virtual void w();
};
class C : virtual public A { //virtual inheritance here
public:
int c;
virtual void x();
};
class D: public B, public C {
public:
int d;
virtual void y();
};
sizeof A is 8
sizeof B is 16
sizeof C is 16
sizeof D is 28 Does it mean 28 = 16(class B) + 16(class C) - 8(class A) + 4 ( what's this? )
My question is , why there is an extra space when virtual inheritance is applied?
What's the underneath rule for the object size in this case?
What's the difference when virtual is applied on all the base classes and on part of the base classes?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
这是定义的所有实现。我正在使用 VC10 Beta2。帮助理解这个东西(虚函数的实现)的关键,你需要了解Visual Studio编译器中的一个秘密开关,/d1reportSingleClassLayoutXXX。我稍后会讲到这一点。
基本规则是,对于任何指向对象的指针,vtable 都需要位于偏移量 0 处。这意味着多重继承需要多个虚函数表。
这里有几个问题,我将从顶部开始:
这就是虚函数的工作原理,您希望基类和派生类共享相同的 vtable 指针(指向派生类中的实现。
这是 C 类的布局,如 /d1reportSingleClassLayoutC 所报告的:
你是对的,有两个 vtable,每个基类一个。这就是多重继承的工作原理;如果 C* 转换为 B*,则指针值将调整 8 个字节。 vtable 仍然需要位于偏移量 0 处才能使虚拟函数调用正常工作。
上述 A 类布局中的 vtable 被视为 C 类的 vtable(当通过 C* 调用时)。
这是本例中类 B 的布局:
正如您所看到的,有一个额外的指针来处理虚拟继承。虚拟继承很复杂。
不,36 字节。同样处理虚拟继承。本例中D的布局:
虚基类指针,比较复杂。基类在虚拟继承中被“组合”。该类将具有指向布局中基类对象的指针,而不是将基类嵌入到类中。如果您有两个使用虚拟继承(“钻石”类层次结构)的基类,它们都将指向对象中的同一个虚拟基类,而不是拥有该基类的单独副本。
重要的一点;没有规则:编译器可以做任何它需要做的事情。
最后一个细节;为了制作我正在编译的所有这些类布局图:
其中 XXX 是您想要查看其布局的结构/类的子字符串匹配。使用它,您可以自己探索各种继承方案的影响,以及添加填充的原因/位置等。
This is all implementation defined. I'm using VC10 Beta2. The key to help understanding this stuff (the implementation of virtual functions), you need to know about a secret switch in the Visual Studio compiler, /d1reportSingleClassLayoutXXX. I'll get to that in a second.
The basic rule is the vtable needs to be located at offset 0 for any pointer to an object. This implies multiple vtables for multiple inheritance.
Couple questions here, I'll start at the top:
This is how virtual functions work, you want the base class and derived class to share the same vtable pointer (pointing to the implementation in the derived class.
This is the layout of class C, as reported by /d1reportSingleClassLayoutC:
You are correct, there are two vtables, one for each base class. This is how it works in multiple inheritance; if the C* is casted to a B*, the pointer value gets adjusted by 8 bytes. A vtable still needs to be at offset 0 for virtual function calls to work.
The vtable in the above layout for class A is treated as class C's vtable (when called through a C*).
This is the layout of class B in this example:
As you can see, there is an extra pointer to handle virtual inheritance. Virtual inheritance is complicated.
No, 36 bytes. Same deal with the virtual inheritance. Layout of D in this example:
Virtual base class pointer, it's complicated. Base classes are "combined" in virtual inheritance. Instead of having a base class embedded into a class, the class will have a pointer to the base class object in the layout. If you have two base classes using virtual inheritance (the "diamond" class hierarchy), they will both point to the same virtual base class in the object, instead of having a separate copy of that base class.
Important point; there are no rules: the compiler can do whatever it needs to do.
And a final detail; to make all these class layout diagrams I am compiling with:
Where XXX is a substring match of the structs/classes you want to see the layout of. Using this you can explore the affects of various inheritance schemes yourself, as well as why/where padding is added, etc.
报价>我的问题是,继承中vptr的数量有什么规则?
没有规则,每个编译器供应商都可以按照他认为合适的方式实现继承语义。
class B:public A {},size = 12。这很正常,B 的一个 vtable 具有两个虚拟方法,vtable 指针 + 2*int = 12
class C:public A,public B {},size = 20。C可以任意扩展 A 或 B 的 vtable。 2*vtable 指针 + 3*int = 20
虚拟继承:这就是您真正触及未记录行为边缘的地方。例如,在 MSVC 中,#pragma vtordisp 和 /vd 编译选项变得相关。 本文中提供了一些背景信息。我研究了几次,并认为编译选项缩写词代表了如果我使用它的话我的代码可能会发生什么。
Quote> My question is, what's the rule about the number of vptrs in inheritance?
There are no rulez, every compiler vendor is allowed to implement the semantics of inheritance the way he sees fit.
class B: public A {}, size = 12. That's pretty normal, one vtable for B that has both virtual methods, vtable pointer + 2*int = 12
class C : public A, public B {}, size = 20. C can arbitrarily extend the vtable of either A or B. 2*vtable pointer + 3*int = 20
Virtual inheritance: that's where you really hit the edges of undocumented behavior. For example, in MSVC the #pragma vtordisp and /vd compile options become relevant. There's some background info in this article. I studied this a few times and decided the compile option acronym was representative for what could happen to my code if I ever used it.
思考这个问题的一个好方法是了解必须采取哪些措施来处理向上转换。我将尝试通过显示您描述的类的对象的内存布局来回答您的问题。
代码示例 #2
内存布局如下:
将指向 B 的指针向上转换为类型 A 将产生相同的地址,并使用相同的 vptr。这就是为什么这里不需要额外的 vptr。
代码示例 #3
正如您所看到的,这里有两个 vptr,正如您所猜测的那样。为什么?因为确实,如果我们从 C 向上转换到 A,我们不需要修改地址,因此可以使用相同的 vptr。但是,如果我们从 C 向上转换到 B,我们确实需要进行修改,相应地,我们在结果对象的开头需要一个 vptr。
因此,除了第一个类之外的任何继承类都需要额外的 vptr(除非该继承类没有虚方法,在这种情况下它没有 vptr)。
代码示例 #4 及其他
当您进行虚拟派生时,您需要一个新指针(称为基指针)来指向派生类的内存布局中的位置。当然,可以有多个基指针。
那么内存布局是怎样的呢?这取决于编译器。在您的编译器中,它可能类似于
但其他编译器可能会在虚拟表中合并基指针(通过使用偏移量 - 这值得另一个问题)。
您需要一个基指针,因为当您以虚拟方式派生时,派生类只会在内存布局中出现一次(如果它也是正常派生的,则可能会出现多次,如您的示例所示),因此它的所有子级都必须指向完全相同的位置。
编辑:澄清 - 这完全取决于编译器,我显示的内存布局在不同的编译器中可能不同。
A good way to think about it is to understand what has to be done to handle up-casts. I'll try to answer your questions by showing the memory layout of objects of the classes you describe.
Code sample #2
The memory layout is as follows:
Upcasting a pointer to B to type A will result in the same address, with the same vptr being used. This is why there's no need for additional vptr's here.
Code sample #3
As you can see, there are two vptr's here, just like you guessed. Why? Because it's true that if we upcast from C to A we don't need to modify the address, and thus can use the same vptr. But if we upcast from C to B we do need that modification, and correspondingly we need a vptr at the start of the resulting object.
So, any inherited class beyond the first will require an additional vptr (unless that inherited class has no virtual methods, in which case it has no vptr).
Code sample #4 and beyond
When you derive virtually, you need a new pointer, called a base pointer, to point to the location in the memory layout of the derived classes. There can be more than one base pointer, of course.
So how does the memory layout look? That depends on the compiler. In your compiler it's probably something like
But other compilers may incorporate base pointers in the virtual table (by using offsets - that deserves another question).
You need a base pointer because when you derive in a virtual fashion, the derived class will appear only once in the memory layout (it may appear additional times if it's also derived normally, as in your example), so all its children must point to the exact same location.
EDIT: clarification - it all really depends on the compiler, the memory layout I showed can be different in different compilers.
所有这一切都是您意识到的完全实现定义的。你不能指望其中任何一个。没有“规则”。
在继承示例中,类 A 和 B 的虚拟表可能如下所示:
正如您所看到的,如果您有一个指向类 B 的虚拟表的指针,那么它作为类 A 的虚拟表也是完全有效的。
在你的 C 类示例中,如果你想一想,没有办法创建一个对于 C 类、A 类和 B 类都有效的虚拟表。因此编译器会创建两个。一个虚拟表对 A 类和 C 类有效(很可能),另一个虚拟表对 A 类和 B 类有效。
All of this is completely implementation defined you realize. You can't count on any of it. There is no 'rule'.
In the inheritance example, here is how the virtual table for classes A and B might look:
As you can see, if you have a pointer to class B's virtual table, it is also perfectly valid as class A's virtual table.
In your class C example, if you think about it, there is no way to make a virtual table that is both valid as a table for class C, class A, and class B. So the compiler makes two. One virtual table is valid for class A and C (mostly likely) and the other is valid for class A and B.
这显然取决于编译器的实现。
无论如何,我认为我可以从下面链接的经典论文给出的实现中总结出以下规则,该规则给出了示例中获得的字节数(D 类除外,它是 36 个字节而不是 32 个字节!!!) :
类 T 的对象的大小为:
所以我们必须回答另一个问题:一个类何时需要另一个 v 表?
规则的结尾(我认为可以应用它来匹配 Terry Mahaffey 在他的答案中解释的内容):)
无论如何,我的建议是阅读 Bjarne Stroustrup(C++ 的创建者)的以下论文,它准确地解释了这些事情:如何许多虚拟表需要虚拟或非虚拟继承......为什么!
这确实是一本很好的读物:
http://www.hpc.unimelb.edu.au/nec/ g1af05e/chap5.html
This obviously depends on the compiler implementation.
Anyway I think that I can sum up the following rules from the implementation given by a classic paper linked below and which gives the number of bytes you get in your examples (except for class D which would be 36 bytes and not 32!!!):
The size of an object of class T is:
So we have to answer another question: When does a class need ANOTHER v-table?
The End of the rules (which I think can be applied to match what Terry Mahaffey has explained in his answer) :)
Anyway my suggestion is to read the following paper by Bjarne Stroustrup (the creator of C++) which explains exactly these things: how many virtual tables are needed with virtual or non virtual inheritance... and why!
It's really a good reading:
http://www.hpc.unimelb.edu.au/nec/g1af05e/chap5.html
我不确定,但我认为这是因为指向 虚拟方法表 的指针
I am not sure but I think that it is because of pointer to Virtual method table