如何在C++中确定对象的大小?

Joe*_*oel 38 c++ memory size memory-management class

例如,假设我有一个类Temp:

class Temp
{
    public:
        int function1(int foo) { return 1; }
        void function2(int bar) { foobar = bar; }

    private:
        int foobar;
};
Run Code Online (Sandbox Code Playgroud)

当我创建Temp类的对象时,我如何计算它需要多少空间,以及它如何在内存中表示(例如| foobar | 4个字节| function1 | 8的8个字节|)

Dre*_*all 62

对于一阶近似,对象的大小是其组成数据成员的大小的总和.你可以肯定它永远不会小于此.

更准确地说,编译器有权在数据成员之间插入填充空间,以确保每个数据成员满足平台的对齐要求.有些平台对齐非常严格,而其他平台(x86)更宽容,但通过正确对齐会表现得更好.因此,即使编译器优化设置也会影响对象大小.

继承和虚函数增加了额外的复杂性.正如其他人所说,你的类的成员函数本身并不占用"每个对象"空间,但是该类接口中虚函数的存在通常意味着存在一个虚拟表,本质上是一个函数指针的查找表,用于动态解析正确的函数实现,以便在运行时调用.通常通过存储在每个对象中的指针来访问虚拟表(vtbl).

派生类对象还包括其基类的所有数据成员.

最后,访问说明符(public,private,protected)通过打包数据成员授予编译器一定的余地.

简短的回答是sizeof(myObj)或sizeof(MyClass)将始终告诉您对象的正确大小,但其结果并不总是很容易预测.


Tod*_*ner 19

sizeof(Temp)
Run Code Online (Sandbox Code Playgroud)

会给你的尺寸.最有可能的是,它是4个字节(给出了很多假设)并且仅适用于int.这些函数不会占用每个对象的任何空间,它们被编译一次,并且每次使用时都由编译器链接.

不可能准确地说出对象布局是什么,但是,标准没有定义对象的二进制表示.

二进制表示有一些需要注意的事情,比如它们不一定是数据成员字节的总和,因为结构填充之类的东西

  • 还有一些像虚拟表指针的东西. (2认同)

And*_*rew 10

我总是想知道这种事情,所以我决定想出一个完整的答案.这是关于你可能期望的,它是可预测的(耶!)!因此,通过以下信息,您应该能够预测班级的大小.

使用Visual Studio Community 2017(版本15.2),在发布模式下禁用所有优化并关闭RTTI(运行时类型信息),我已确定以下内容:


简短回答:

首先:

  • 在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>bytes
  • 继承一个类并覆盖它的函数会增加一个vtable(结论部分提供的进一步解释)并且是<size of pointer>字节
  • 简单地声明一个虚函数也会添加一个vtable,使其成为<size of pointer>字节
  • 空类的虚拟类继承(有或没有成员函数)也会添加一个vtable,并使类<size of pointer>字节成为可能
  • 非空类的虚拟类继承也添加了一个vtable,但它有点复杂:它向总数中添加了 <size of pointer>字节,所有成员变量包装<size of pointer>成所需的字节数增量<total size of member variables>- 是的,你读的是正确的......(看看我对结论中发生的事情的猜测......)

请注意,我甚至尝试使用function()cout一些文本,创建类的实例,并调用该函数; 它不会改变函数类的大小(它不是优化)!我有点惊讶,但它确实有意义:成员函数不会改变,因此它们可以存储在类本身的外部.

结论:

  • 空类是1个字节,因为这是它在内存中存在所需的最小值.但是,一旦添加了数据或vtable数据,就开始以0字节计数.
  • 添加(非虚拟)成员函数对大小没有任何作用,因为成员函数存储在外部.
  • 将成员函数声明为虚拟(即使该类未被覆盖!)或覆盖子类中的成员函数,也会添加所谓的"vtable"或"虚函数表",这允许动态调度(这实际上是超级棒的使用虽然我强烈建议使用它).这个vtable消耗<size of pointer>字节,向<size of pointer>所述类添加字节.当然,这个vtable每个类只能存在一次(无论是它还是不存在).
  • 添加成员变量会增加该成员变量的类大小,无论所述成员变量是在父类还是子类中(当然,父类仍保持其自己的大小).
  • 虚拟类继承是唯一变得复杂的部分...所以...我认为经过一些实验后发生的事情是:类的大小实际上<size of pointer>一次以字节为单位递增,即使它不需要消耗那个内存,我猜是因为它为每个<size of pointer>字节的内存或其他东西添加了一个vtable"helper block" ...

答案很长:

我使用这段代码确定了所有这些:

#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");
}
Run Code Online (Sandbox Code Playgroud)

输出:

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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

如果您想获得有关多重继承的信息,请自己弄明白!-.-


Pau*_*rie 8

如果需要有关如何在运行时在内存中表示对象的详细信息,则可以查看ABI(应用程序二进制接口)规范.您需要查看确定编译器实现的ABI; 例如,GCC 3.2及更高版本实现了Itanium C++ ABI.


eph*_*ent 6

方法属于类,而不是任何特定的实例化对象.

除非存在虚方法,否则对象的大小是其非静态成员的大小的总和,以及成员之间用于对齐的可选填充.成员可能会在内存中按顺序排列,但规范并不保证具有不同访问规范的部分之间的排序,也不保证相对于超类的布局的排序.

使用虚拟方法时,vtable和其他RTTI信息可能会有额外的空间.

在大多数平台上,可执行代码都位于.text可执行文件或库的只读(或类似命名)部分,并且永远不会复制到任何地方.当class Temp有方法时public: int function1(int),Temp元数据可能具有指向_ZN4Temp9function1Ei实际实现的(受损编译名称可能根据编译器而不同)函数的指针,但当然它永远不会包含嵌入的可执行代码.