为什么不尝试打印布局?来自system V abi
这是一个在线C ++测试问题,已经完成。
#include<iostream>
using namespace std;
class A
{
};
class B
{
int i;
};
class C
{
void foo();
};
class D
{
virtual void foo();
};
class E
{
int i ;
virtual void foo();
};
class F
{
int i;
void foo();
};
class G
{
void foo();
int i;
void foo1();
};
class H
{
int i ;
virtual void foo();
virtual void foo1();
};
int main()
{
cout <<"sizeof(class A) : " << sizeof(A) << endl ;
cout <<"sizeof(class B) adding the member int i : " << sizeof(B) << endl ;
cout <<"sizeof(class C) adding the member void foo() : " << sizeof(C) << endl ;
cout <<"sizeof(class D) after making foo virtual : " << sizeof(D) << endl ;
cout <<"sizeof(class E) after adding foo virtual , int : " << sizeof(E) << endl ;
cout <<"sizeof(class F) after adding foo , int : " << sizeof(F) << endl ;
cout <<"sizeof(class G) after adding foo , int : " << sizeof(G) << endl ;
G g;
cout <<"sizeof(class G) after adding foo , int : " << sizeof(g) << endl ;
cout <<"sizeof(class H) after adding int 2 virtual " << sizeof(H) << endl ;
return 0;
}
输出:
sizeof(class A) : 1
sizeof(class B) adding the member int i : 4
sizeof(class C) adding the member void foo() : 1
sizeof(class D) after making foo virtual : 8
sizeof(class E) after adding foo virtual , int : 16
sizeof(class F) after adding foo , int : 4
sizeof(class G) after adding foo , unsigned int : 4
sizeof(class g) after adding foo , unsigned int : 4
sizeof(class H) after adding int 2 virtual 16
我的问题:
为什么siszeof(A)
也为1而sizeof(C)
也为1?
为什么siszeof(H)
是16但sizeof(G)
是4?
为什么siszeof(E)
是16但sizeof(F)
是4?
为什么siszeof(D)
是8但sizeof(E)
是16?
我的猜测:
虚拟函数是一个8字节的指针。但是,我不知道为什么E
大小为16?向空类添加函数不会改变其大小?
感谢您的帮助。
感谢
首先,虚拟函数不是具有8个字节的指针。在C ++中,只能保证sizeof(char)
为任意数量的字节。
其次,只有类中的第一个虚函数会增加其大小(依赖于编译器,但大多数情况下-如果不是全部-就是这样)。所有后续方法都不会。非虚函数不会影响类的大小。
之所以会发生这种情况,是因为类实例不保存指向方法本身的指针,而是指向virtual function table的指针,该指针每个类一个。
因此,如果您有:
class A
{
virtual void foo();
}
和
class B
{
virtual void goo();
virtual void test();
static void m();
void x();
}
您将获得sizeof(A) == sizeof(B)
。
现在:
为什么siszeof(A)为1,sizeof(C)也为1?
A
和C
的大小为1,只是因为不允许类的大小为0。这些函数与之无关。这只是一个虚拟字节。
为什么siszeof(H)是16,而sizeof(G)是4?
G
只有一个成员占用内存-int
。在您的平台上,sizeof(int) == 4
。 H
除int
外,还具有指向vftable
的指针(虚拟功能表,请参见上文)。此大小,int大小和分配与编译器有关。
为什么siszeof(E)是16但sizeof(F)是4?
上面解释-非虚拟方法不会占用该类中的内存。
为什么siszeof(D)是8,而sizeof(E)是16?
D
仅包含vftable
指针,在您的平台上显然是8个字节。 E
也有一个int,并且vftable
对齐为8个字节。所以就像:
class E
4 bytes for int | 4 padding bytes | 8 bytes for vftable pointer |
| x | x | x | x | | | | | v | v | v | v | v | v | v | v |
为什么siszeof(A)为1,sizeof(C)也为1?
C
中的函数不是虚拟的,因此该类不需要vtable指针,因此它不需要的存储空间比A
多。 A
和C
都不需要任何存储,但是由于语言要求同一个类的不同实例具有不同的指针,所以它们的大小不能为零-因此编译器将它们的大小减小到最小,即1个字节。
为什么siszeof(H)是16,而sizeof(G)是4?
G
没有虚函数,因此它需要存储的只是int,在您的编译器和体系结构上为4字节。
H
具有虚函数,因此该类需要包含一个int
和一个vtable指针。所有广泛使用的编译器将vtable指针存储在类的开头,因此布局为{vptr,int},如果在64位主机上,则为8 + 4 = 12字节。
但是,编译器可以自由地将其填充为16个字节,因此,如果在数组中分配H
的多个实例,则所有这些实例都将按字对齐。这很重要,因为如果指针未字对齐,则访问指针(即此处的vtable指针)会对性能产生重大影响。
为什么siszeof(E)是16但sizeof(F)是4?
E具有虚函数,因此需要一个vtable ptr,因此其布局类似于H
的布局。 F
没有虚函数,它只有一个整数,因此其布局类似于G
的布局。因此答案与G
和H
相同。
成员/函数的顺序在这里无关紧要,因为只有一个成员变量,而如果有一个成员变量,则vtable ptr始终排在第一位。
为什么siszeof(D)是8,而sizeof(E)是16?
D
没有成员变量,但是具有虚函数,因此需要一个vtable指针。 vtable指针是唯一需要的东西,因此其大小为sizeof(void*)
,即8个字节。 E
需要与D
相同,再加上4个字节作为整数,并且编译器将其舍入为16个字节以进行对齐。
之所以如此,是因为C ++标准禁止大小为0的类/结构。这就是为什么空的结构/类的大小为1的原因。我觉得这很烦人,但是他们对此有一些道理。
这是int的大小,简单明了:)
这是一个空结构的大小(请参阅A)。非虚函数完全不影响对象大小。您无需在对象中存储任何内容即可调用其非虚拟成员函数。
我假设您正在构建64位应用程序。具有至少一个虚函数的任何类都具有指向虚方法表的指针。即使将对象指针强制转换为某个父类,这也允许您调用正确的虚函数。通常将指针称为vtable。在Wiki上阅读更多内容:http://en.wikipedia.org/wiki/Virtual_method_table
我认为大小8来自该64位指针。
为了存储指针和int,从技术上讲,您需要12个字节。但是,指针必须对齐8个字节。现在设想自己创建一个对象E的数组A。A[0].vtable
的地址为&A + 0,A[0].i
的地址为&A+8
,A[1].vtable
的地址为&A+12
–糟糕,我们有一个问题,12不能被8整除。这就是编译器创建padding的原因。它添加了其他无用的字节,以使对象在数组中正确对齐。在这种情况下,可以除以8的数的最小数为16。因此是大小。
与C的情况相同-非虚函数完全不影响大小,因此您的大小匹配B。
sizeof(G)== 4-与F相同>]
sizeof(H)== 16
虚拟功能的数量无关紧要。您的对象中仍然只有一个vtable指针。如果放置更多虚拟函数,虚拟表将变大,但对象本身将变大。在许多面向对象的程序中,您通常最终会拥有许多虚函数。将指针直接存储在对象本身会很浪费。
这就是H的大小(和说明)与E的大小匹配的原因。>>
为什么不尝试打印布局?来自system V abi
例如(输入您的代码并记住将sizeof
应用于您要检查的类型)
struct Base1 {
virtual int method_base_11() {
return 11;
}
virtual ~Base1() = default;
};
struct Base2 {
virtual int method_base_21() {
return 22;
}
virtual ~Base2() = default;
};
struct Foo: public Base1, public Base2 {
int a;
};
int main() {
Foo foo;
foo.method_base_21();
return sizeof(Foo);
}
输出,
$ clang -cc1 -std=c++11 -fdump-record-layouts foo.cc
*** Dumping AST Record Layout
0 | struct Base1
0 | (Base1 vtable pointer)
| [sizeof=8, dsize=8, align=8,
| nvsize=8, nvalign=8]
*** Dumping AST Record Layout
0 | struct Base2
0 | (Base2 vtable pointer)
| [sizeof=8, dsize=8, align=8,
| nvsize=8, nvalign=8]
*** Dumping AST Record Layout
0 | struct Foo
0 | struct Base1 (primary base)
0 | (Base1 vtable pointer)
8 | struct Base2 (base)
8 | (Base2 vtable pointer)
16 | int a
| [sizeof=24, dsize=20, align=8,
| nvsize=20, nvalign=8]
带填充和虚拟函数的int大小= 18字节
简单函数字节= 1虚函数= 8
而且您不能简单地添加所有字节,因此请在Google上进行填充检查的概念。
以不同的顺序声明会改变类的大小
为什么不尝试打印布局?来自system V abi
带填充和虚拟函数的int大小= 18字节