如何确定 C++ 中对象的大小?
Posted
技术标签:
【中文标题】如何确定 C++ 中对象的大小?【英文标题】:How do you determine the size of an object in C++? 【发布时间】:2010-10-30 13:57:30 【问题描述】:例如,假设我有一个 Temp 类:
class Temp
public:
int function1(int foo) return 1;
void function2(int bar) foobar = bar;
private:
int foobar;
;
当我创建一个 Temp 类的对象时,我将如何计算它需要多少空间,以及它在内存中的表示方式(例如| foobar 为 4 个字节| function1 为 8 个字节 | 等 | )
【问题讨论】:
my.safaribooksonline.com/0201834545">Here</… 一本关于这个主题的好书。 这个问题可能更具描述性,标题为“”。 【参考方案1】:对于一阶近似,对象的大小是其组成数据成员大小的总和。你可以肯定它永远不会小于这个。
更准确地说,编译器有权在数据成员之间插入填充空间,以确保每个数据成员满足平台的对齐要求。一些平台对对齐非常严格,而另一些(x86)则更宽容,但在正确对齐的情况下会表现得更好。因此,即使是编译器优化设置也会影响对象大小。
继承和虚函数增加了额外的复杂性。正如其他人所说,您的类的成员函数本身并不占用“每个对象”空间,但该类接口中虚函数的存在通常意味着虚表的存在,本质上是用于查找函数指针的表动态解析正确的函数实现以在运行时调用。虚拟表 (vtbl) 通常通过存储在每个对象中的指针来访问。
派生类对象还包括其基类的所有数据成员。
最后,访问说明符(public、private、protected)赋予编译器一定的数据成员打包余地。
简短的回答是 sizeof(myObj) 或 sizeof(MyClass) 总是会告诉您对象的正确大小,但其结果并不总是容易预测。
【讨论】:
“你的类的成员函数本身不占用“每个对象”的空间”。没错,但即使他们这样做了,这些仍然是函数,而不是数据,因此它们不会占用sizeof(myObject)
可以考虑的任何可测量空间。
非常有帮助的答案。一个建议是,当你说 [s]ome platforms are very strict about alignment
时,如果你能举一个例子可能会更有帮助,因为你提供 x86
来说明一个宽容的平台。【参考方案2】:
sizeof(Temp)
会给你尺寸。最有可能的是,它是 4 个字节(给出了很多假设),并且仅适用于 int。这些函数在每个对象的基础上不占用任何空间,它们只编译一次,每次使用时都由编译器链接。
无法准确说明对象布局是什么,但是,标准并未定义对象的二进制表示。
由于structure padding 之类的原因,使用二进制表示需要注意一些事项,例如它们不一定是数据成员字节的总和。
【讨论】:
@harshath.jr: 是的,但是显示的类中没有虚函数,所以没有虚表【参考方案3】:我一直想知道这种事情,所以我决定想出一个完整的答案。这是关于你可能期望的,它是可以预测的(耶)!因此,通过以下信息,您应该能够预测类的大小。
使用 Visual Studio Community 2017(版本 15.2),在禁用所有优化并关闭 RTTI (Run-time Type Information) 的发布模式下,我确定了以下内容:
简短的回答:
首先:
在 32 (x86) 位中,<size of pointer> == 4
字节
在 64 (x64) 位中,<size of pointer> == 8
字节
当我说“虚拟类继承”时,我的意思是例如:class ChildClass: virtual public ParentClass
现在,我的发现是:
空类是 1 个字节 空类的继承还是1字节 具有函数的空类仍然是 1 个字节(?!请参阅下面的注意进行解释) 用函数继承空类还是1字节 向空类添加变量是<size of variable>
字节
用一个变量继承一个类并添加另一个变量是<size of variables>
字节
继承一个类并覆盖它的函数会添加一个 vtable(在结论部分提供了进一步的解释)并且是<size of pointer>
bytes
简单地声明一个函数 virtual 也增加了一个 vtable,使它成为<size of pointer>
bytes
空类(有或没有成员函数)的虚类继承也增加了一个vtable,并使类<size of pointer>
字节
非空类的虚类继承也添加了一个 vtable,但它变得有些复杂:它添加<size of pointer>
字节总数,包装所有成员变量以<size of pointer>
字节递增,以覆盖<total size of member variables>
- 是的,你没看错......(请参阅我对结论中发生的事情的猜测...... )
注意,我什至尝试让 function() 输出一些文本、创建类的实例并调用该函数;它不会改变函数类的大小(这不是优化)!我有点惊讶,但这实际上是有道理的:成员函数不会改变,因此它们可以存储在类本身的外部。
结论:
空类是 1 个字节,因为这是它在内存中存在所需的最小值。 一旦添加数据或 vtable 数据,则从 0 字节开始计数。 添加(非虚拟)成员函数对大小没有任何影响,因为成员函数存储在外部。 将成员函数声明为虚拟(即使该类没有被覆盖!)或在子类中覆盖成员函数会添加所谓的"vtable" or "virtual function table",它允许Dynamic Dispatch(这真的超级棒不过要使用,我强烈建议使用它)。此 vtable 消耗<size of pointer>
字节,将 <size of pointer>
字节添加到所述类。当然,这个 vtable 每个类只能存在一次(存在或不存在)。
添加成员变量会通过该成员变量增加类的大小,无论所述成员变量是在父类还是子类中(当然,父类保持自己的大小)。
虚拟类继承是唯一变得复杂的部分......所以......我认为经过一些实验后发生的事情是:类的大小实际上每次增加<size of pointer>
字节,即使它不需要消耗那么多内存,我猜是因为它为每个<size of pointer>
字节的内存或其他东西添加了一个vtable“帮助块”......
长答案:
我用这段代码确定了所有这些:
#include <iostream>
using namespace std;
class TestA
;
class TestB: public TestA
;
class TestC: virtual public TestA
;
class TestD
public:
int i;
;
class TestE: public TestD
public:
int j;
;
class TestF: virtual public TestD
public:
int j;
;
class TestG
public:
void function()
;
class TestH: public TestG
public:
void function()
;
class TestI: virtual public TestG
public:
void function()
;
class TestJ
public:
virtual void function()
;
class TestK: public TestJ
public:
void function() override
;
class TestL: virtual public TestJ
public:
void function() override
;
void main()
cout << "int:\t\t" << sizeof(int) << "\n";
cout << "TestA:\t\t" << sizeof(TestA) << "\t(empty class)\n";
cout << "TestB:\t\t" << sizeof(TestB) << "\t(inheriting empty class)\n";
cout << "TestC:\t\t" << sizeof(TestC) << "\t(virtual inheriting empty class)\n";
cout << "TestD:\t\t" << sizeof(TestD) << "\t(int class)\n";
cout << "TestE:\t\t" << sizeof(TestE) << "\t(inheriting int + int class)\n";
cout << "TestF:\t\t" << sizeof(TestF) << "\t(virtual inheriting int + int class)\n";
cout << "TestG:\t\t" << sizeof(TestG) << "\t(function class)\n";
cout << "TestH:\t\t" << sizeof(TestH) << "\t(inheriting function class)\n";
cout << "TestI:\t\t" << sizeof(TestI) << "\t(virtual inheriting function class)\n";
cout << "TestJ:\t\t" << sizeof(TestJ) << "\t(virtual function class)\n";
cout << "TestK:\t\t" << sizeof(TestK) << "\t(inheriting overriding function class)\n";
cout << "TestL:\t\t" << sizeof(TestL) << "\t(virtual inheriting overriding function class)\n";
cout << "\n";
system("pause");
输出:
32 (x86) 位:
int: 4
TestA: 1 (empty class)
TestB: 1 (inheriting empty class)
TestC: 4 (virtual inheriting empty class)
TestD: 4 (int class)
TestE: 8 (inheriting int + int class)
TestF: 12 (virtual inheriting int + int class)
TestG: 1 (function class)
TestH: 1 (inheriting function class)
TestI: 4 (virtual inheriting function class)
TestJ: 4 (virtual function class)
TestK: 4 (inheriting overriding function class)
TestL: 8 (virtual inheriting overriding function class)
64 (x64) 位:
int: 4
TestA: 1 (empty class)
TestB: 1 (inheriting empty class)
TestC: 8 (virtual inheriting empty class)
TestD: 4 (int class)
TestE: 8 (inheriting int + int class)
TestF: 24 (virtual inheriting int + int class)
TestG: 1 (function class)
TestH: 1 (inheriting function class)
TestI: 8 (virtual inheriting function class)
TestJ: 8 (virtual function class)
TestK: 8 (inheriting overriding function class)
TestL: 16 (virtual inheriting overriding function class)
如果你想了解关于多重继承的信息,去弄清楚你该死的自己! -.-
【讨论】:
【参考方案4】:如果您想了解有关对象在运行时如何在内存中表示的详细信息,则可以查看 ABI (Application Binary Interface) 规范。您需要确定您的编译器实现了哪个 ABI;例如,GCC 3.2 及以上版本实现了Itanium C++ ABI。
【讨论】:
【参考方案5】:方法属于类,而不是任何特定的实例化对象。
除非有虚方法,否则对象的大小是其非静态成员大小的总和,加上成员之间用于对齐的可选填充。成员可能会在内存中按顺序排列,但规范不保证具有不同访问规范的部分之间的顺序,也不保证相对于超类布局的顺序。
如果存在虚拟方法,vtable 和其他 RTTI 信息可能会占用额外的空间。
在大多数平台上,可执行代码位于可执行文件或库的只读.text
(或类似名称)部分,并且永远不会被复制到任何地方。当class Temp
有一个方法public: int function1(int)
时,Temp
元数据可能有一个指向实际实现的_ZN4Temp9function1Ei
函数的指针(损坏的名称可能因编译器而异),但它肯定永远不会包含可执行代码嵌入。
【讨论】:
【参考方案6】:成员函数不考虑特定类的对象的大小。对象的大小仅取决于成员变量。对于包含虚函数的类,VPTR 被添加到对象布局中。所以对象的大小基本上是成员变量的大小 + VPTR 的大小。有时这可能不是真的,因为编译器试图在 DWORD 边界处定位成员变量。
【讨论】:
【参考方案7】:如果您使用的是 Microsoft Visual C++,则有一个编译器选项可以告诉您您的对象实际有多大:/d1reportSingleClassLayout
除了 Lavavej http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-3-of-n 的这个视频之外,它没有被记录在案
【讨论】:
【参考方案8】:如果您想检查特定结构的布局,也可以使用offsetof(s,member)
宏。它告诉你一个特定成员离结构的基地址有多远:
struct foo
char *a;
int b;
;
// Print placement of foo's members
void printFoo()
printf("foo->a is %zu bytes into a foo\n", offsetof(struct foo, a));
printf("foo->b is %zu bytes into a foo\n", offsetof(struct foo, b));
int main()
printFoo();
return 0;
将在典型的 32 位机器上打印:
foo->a is 0 bytes into a foo
foo->b is 4 bytes into a foo
而在典型的 64 位机器上,它会打印
foo->a is 0 bytes into a foo
foo->b is 8 bytes into a foo
【讨论】:
【参考方案9】:This 可能会有所帮助。
此外,类函数的表示方式与任何其他函数一样。 C++ 对函数的唯一魔力是修改函数名称,以在特定类中使用一组特定参数唯一标识特定函数。
【讨论】:
【参考方案10】:有一个实用程序调用pahole
(用于'Poke-A-HOLE')名义上旨在研究对象布局如何被填充,但对于总体上可视化对象大小和布局非常有用。
【讨论】:
【参考方案11】:类的对象的大小等于该类的所有数据成员的大小之和。例如,如果我有课
class student
private:
char name[20];
int rollno, admno;
float marks;
public:
float tmarks, percentage;
void getdata();
void putdata();
;
现在,如果我创建这个类的一个对象,比如s1
,那么这个对象的大小将是 36 字节:
[20(name)+2(rollno)+2(admno)+4(marks)+4(tmarks)+4(percentage)]
【讨论】:
欢迎来到 ***。您的答案与例如相同的内容由 Drew Hall 撰写,除了您省略了基本部分,例如关于填充和关于多态性的影响。如果您对已回答的问题给出答案,请确保您的回答比现有问题有所改进。以上是关于如何确定 C++ 中对象的大小?的主要内容,如果未能解决你的问题,请参考以下文章