当作为公共虚拟继承时,为什么类的大小会增加
Why the size of class is increasing when inherit as public virtual?
为什么b
和c
类的大小是4
?virtual关键字是否创建了一个vptr(vptr不带virtual函数存在吗?)还是其他什么?请分享你的想法。
#include<iostream>
using namespace std;
class a{
};
class b:public virtual a{
};
class c:public virtual a{
};
class d:public b, public c{
};
main(){
cout<<sizeof(a)<<"n"; //1
cout<<sizeof(b)<<"n"; //4
cout<<sizeof(c)<<"n"; //4
cout<<sizeof(d)<<"n"; //8
}
如果virtual
不在任何地方使用,则o/p
变为:1 1 1 2;预期行为。
是的,由于虚拟继承vptr被编译器创建,即使没有虚拟函数。为了理解如何使用gcc编译器,我们可以使用(-fdump-tree-all)标志,并查看中间文件(*.class),其中可以找到vptr和vtable布局。
$ g++ -fdump-tree-all -Wall basic.cpp -o basic
现在我们可以从中间的basic.class文件中找到关于vptr和vtable布局的信息。
//a类信息
Class a
size=1 align=1
base size=0 base align=1
a (0x0x7fc8d707e2a0) 0 empty
//b类VPTR和size信息
Vtable for b
b::_ZTV4b: 3u entries
0 0u
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI4bbbb)
VTT for b
b::_ZTT4b: 1u entries
0 ((& b::_ZTV4b) + 24u)
Class b
size=8 align=8
base size=8 base align=8
b (0x0x7fc8d7053e38) 0 nearly-empty
vptridx=0u vptr=((& b::_ZTV4bbbb) + 24u)
a (0x0x7fc8d707e300) 0 empty virtual
vbaseoffset=-24
//c类VPTR和size信息
Vtable for c
c::_ZTV4c: 3u entries
0 0u
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI4cccc)
VTT for c
c::_ZTT4c: 1u entries
0 ((& c::_ZTV4c) + 24u)
Class c
size=8 align=8
base size=8 base align=8
c (0x0x7fc8d7053ea0) 0 nearly-empty
vptridx=0u vptr=((& c::_ZTV4c) + 24u)
a (0x0x7fc8d707e360) 0 empty virtual
vbaseoffset=-24
//d类VPTR和尺寸信息
Vtable for d
d::_ZTV4d: 6u entries
0 0u
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI4d)
24 18446744073709551608u
32 (int (*)(...))-8
40 (int (*)(...))(& _ZTI4d)
Construction vtable for b (0x0x7fc8d70f8000 instance) in d
d::_ZTC4d0_4b: 3u entries
0 0u
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI4b)
Construction vtable for c (0x0x7fc8d70f8068 instance) in d
d::_ZTC4d8_4c: 3u entries
0 18446744073709551608u
8 (int (*)(...))0
16 (int (*)(...))(& _ZTI4c)
VTT for d
d::_ZTT4d: 4u entries
0 ((& d::_ZTV4d) + 24u)
8 ((& d::_ZTC4d0_4b) + 24u)
16 ((& d::_ZTC4d8_4c) + 24u)
24 ((& d::_ZTV4d) + 48u)
Class d
size=16 align=8
base size=16 base align=8
d (0x0x7fc8d70cca80) 0
vptridx=0u vptr=((& d::_ZTV4d) + 24u)
b (0x0x7fc8d70f8000) 0 nearly-empty
primary-for d (0x0x7fc8d70cca80)
subvttidx=8u
a (0x0x7fc8d707e3c0) 0 empty virtual
vbaseoffset=-24
c (0x0x7fc8d70f8068) 8 nearly-empty
subvttidx=16u vptridx=24u vptr=((& d::_ZTV4d) + 48u)
a (0x0x7fc8d707e3c0) alternative-path
这解释了这里发生了什么,以及为什么和如何根据创建的vptr的数量来改变对象的大小。我的机器是x86_64 GNU/Linux,因此指针的大小将是8,而不是原来的例子中的4。
当使用非虚拟继承时,对象的完整布局在编译时确定。当使用虚拟继承时,情况并非如此——在这种情况下,基子对象的偏移量是在运行时确定的。
如何实现的细节会因编译器而异,但通常会涉及一个或多个额外的指针。请看这个问题的答案,有一个解释。
注意,如果你有虚方法,这与虚表指针是分开的。正如您在示例中所指出的,在您的示例中没有虚拟方法。
Stroustrup在c++ 7.1节的多重继承。
表示虚拟基类a
对象的对象不能被放置在相对于两者的固定位置所有对象中的b
和c
。因此,指向a
的指针必须存储在所有直接访问a
的对象中对象,以允许独立于其相对位置的访问。
Scott Meyers的《More Effective c++》,Item 24, "理解虚函数、多重继承、虚基类和RTTI的代价"虽然没有深入讨论这个问题,但解释了虚基类如何通过在两个直接派生类中为虚基类添加指针来增加对象大小。它可能与虚函数没有任何关系。编译器可能会发现这是克服b
和c
本身可能具有不同大小以及a
成员访问可以通过b
, c
或d
中的任何一个的问题的最简单的方法。
这里有很多"可能",这是因为整个事情完全是特定于编译器的,因为c++标准没有指定类的内存布局。奇怪的是,在互联网上很难找到编译器供应商提供的关于虚拟基类的内存布局的真正好的权威的文档(这可能是因为虚拟继承首先是一个很少使用的c++语言特性)。
对于GCC,您可能会发现-fdump-class-hierarchy
编译器选项很有用。以下是它在我的机器上为您的示例生成的内容(删除标准库的内容):
Class a
size=1 align=1
base size=0 base align=1
a (0x0x344a0a8) 0 empty
Vtable for b
b::_ZTV1b: 3u entries
0 0u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI1b)
VTT for b
b::_ZTT1b: 1u entries
0 ((& b::_ZTV1b) + 12u)
Class b
size=4 align=4
base size=4 base align=4
b (0x0x3460b40) 0 nearly-empty
vptridx=0u vptr=((& b::_ZTV1b) + 12u)
a (0x0x344a0e0) 0 empty virtual
vbaseoffset=-12
Vtable for c
c::_ZTV1c: 3u entries
0 0u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI1c)
VTT for c
c::_ZTT1c: 1u entries
0 ((& c::_ZTV1c) + 12u)
Class c
size=4 align=4
base size=4 base align=4
c (0x0x3460d40) 0 nearly-empty
vptridx=0u vptr=((& c::_ZTV1c) + 12u)
a (0x0x344a118) 0 empty virtual
vbaseoffset=-12
Vtable for d
d::_ZTV1d: 6u entries
0 0u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI1d)
12 4294967292u
16 (int (*)(...))-4
20 (int (*)(...))(& _ZTI1d)
Construction vtable for b (0x0x3460f00 instance) in d
d::_ZTC1d0_1b: 3u entries
0 0u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI1b)
Construction vtable for c (0x0x3460f40 instance) in d
d::_ZTC1d4_1c: 3u entries
0 4294967292u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI1c)
VTT for d
d::_ZTT1d: 4u entries
0 ((& d::_ZTV1d) + 12u)
4 ((& d::_ZTC1d0_1b) + 12u)
8 ((& d::_ZTC1d4_1c) + 12u)
12 ((& d::_ZTV1d) + 24u)
Class d
size=8 align=4
base size=8 base align=4
d (0x0x3460ec0) 0
vptridx=0u vptr=((& d::_ZTV1d) + 12u)
b (0x0x3460f00) 0 nearly-empty
primary-for d (0x0x3460ec0)
subvttidx=4u
a (0x0x344a150) 0 empty virtual
vbaseoffset=-12
c (0x0x3460f40) 4 nearly-empty
subvttidx=8u vptridx=12u vptr=((& d::_ZTV1d) + 24u)
a (0x0x344a150) alternative-path
对于非虚拟继承,内存布局要简单得多:
Class a
size=1 align=1
base size=0 base align=1
a (0x0x344a0a8) 0 empty
Class b
size=1 align=1
base size=1 base align=1
b (0x0x346cb40) 0 empty
a (0x0x344a0e0) 0 empty
Class c
size=1 align=1
base size=1 base align=1
c (0x0x346cbc0) 0 empty
a (0x0x344a118) 0 empty
Class d
size=2 align=1
base size=2 base align=1
d (0x0x346cc40) 0 empty
b (0x0x346cc80) 0 empty
a (0x0x344a150) 0 empty
c (0x0x346ccc0) 1 empty
a (0x0x344a188) 1 empty
因此,GCC显然是通过vptrs实现虚拟继承的,并且不关心在不需要时将其优化掉。
我猜B对于vtable更大(即使没有函数)。
实现当然取决于编译器和所有这些,但我记得在Delphi的时候,它会在类的实例前面创建4字节的虚拟表(例如&myInstance-4)。
我猜它必须创建一个表,即使没有函数,也许只是在那里放一个0。
请记住,处理虚函数的方式必须在整个系统中兼容,所以如果你要加载一个动态库来查看你的类,它需要知道如何读取它们。我怀疑,由于这个原因,编译器不能完全摆脱多余的空间。
- 为什么我的代码在输出中增加了93天
- 为什么要增加导致崩溃的指针
- 为什么"i"在循环的每次迭代中都没有增加?(C++)
- 为什么当通过 TCP 发送的消息速率增加时,请求-响应消息对的延迟会降低?
- 为什么在C++中增加指针后打印了一个值而不是 NULL/0?
- 为什么这个 x 值在这个循环映射中没有增加?C++
- C++为什么原始指针不会增加shared_ptr的引用计数?
- 为什么 SFINAE 没有给出增加布尔值的正确结果?
- 为什么将鼠标悬停在静态 Win32 控件上会增加内存并删除我的 GUI?
- 为什么Virtual继承2类会增加对象大小
- 为什么未达到的 try-catch 块会增加运行时时间
- 为什么内存访问时间远远超过CPU缓存大小时会增加
- C++,为什么多维数组的一个元素的增加似乎增加了另一个元素
- WINAPI C/C++ -> 为什么二进制大小急剧增加?(从VS2013切换到VS 2015)
- 为什么添加虚拟方法在C 中增加了类大小
- 为什么 c++ 字符数组大小增加 1
- 为什么在增加 -fconstexpr-步数后无法解析常量表达式?
- 为什么要在我的OpenMP代码中增加执行时间
- 调整矢量大小后,为什么我不能增加矢量
- 为什么在保留向量中插入空结构会增加内存