Home About Me

What Really Determines a C++ Class’s Size: A Practical Look at sizeof

In C++, being able to reason about the memory footprint of a class is a basic skill. sizeof(class) is also a common interview topic, but the answer is rarely just “add up the members.” A class’s size can be affected by several things at once: the types of its data members, whether it has static members, whether it defines ordinary member functions or virtual functions, and how alignment rules reshape the final layout in memory.

Unless otherwise stated, the examples below assume a 64-bit machine using the MSVC X86 compiler.

Although the discussion focuses on classes, the same way of thinking largely applies to struct in C++ as well.

What affects the size of a class?

Data members

The most direct factor is the set of non-static data members stored inside each object.

Different types occupy different amounts of memory. For example, an int typically takes 4 bytes, while a double takes 8 bytes. When several members of different types appear in the same class, the object size is not simply the raw sum of those sizes, because alignment rules may insert padding between members or at the end of the object.

const int _a;    // 4B
char _b;         // 1B
const short _c;  // 2B
double _d;       // 8B

A commonly used size reference is as follows:

<table> <thead> <tr> <th>Data type</th> <th>32-bit</th> <th>64-bit</th> <th>Notes</th> </tr> </thead> <tbody> <tr> <td>bool</td> <td>1 byte</td> <td>1 byte</td> <td>Usually uses only 1 bit logically, but 1 byte is allocated for alignment and access convenience.</td> </tr> <tr> <td>char</td> <td>1 byte</td> <td>1 byte</td> <td>—</td> </tr> <tr> <td>short</td> <td>2 byte</td> <td>2 byte</td> <td>—</td> </tr> <tr> <td>int</td> <td>4 byte</td> <td>4 byte</td> <td>—</td> </tr> <tr> <td>long</td> <td>4 byte</td> <td>8 byte</td> <td>Typically larger on a 64-bit system.</td> </tr> <tr> <td>long long</td> <td>8 byte</td> <td>8 byte</td> <td>Extended integer type introduced in C++11; size is fixed here.</td> </tr> <tr> <td>float</td> <td>4 byte</td> <td>4 byte</td> <td>—</td> </tr> <tr> <td>double</td> <td>8 byte</td> <td>8 byte</td> <td>—</td> </tr> <tr> <td>long double</td> <td>16 byte</td> <td>16 byte</td> <td>Extended floating-point type introduced in C++11; size is fixed here.</td> </tr> <tr> <td>pointer</td> <td>4 byte</td> <td>8 byte</td> <td>Depends on address width: 4 bytes on 32-bit systems, 8 bytes on 64-bit systems.</td> </tr> </tbody> </table>

Static data members

Static members are different from ordinary data members: they are not stored inside each object instance. Instead, they exist as class-level shared storage outside individual objects.

That means static data members do not directly affect the size of a class instance.

static int _k_a;           // 0B
static const int _k_b = 1; // 0B

Member functions

Ordinary member functions also do not increase object size.

Their code is shared by all instances of the class rather than duplicated in every object.

void func() const {};      // 0B
static char sfunc() {};    // 0B

Virtual functions

Virtual functions are different.

If a class has at least one virtual function, the compiler typically adds a virtual table (vtable) for the class, and each object usually stores a pointer to that table. This extra pointer increases the size of the object.

The vtable pointer size follows the platform word size: 4 bytes on a 32-bit system and 8 bytes on a 64-bit system.

virtual int vfunc() {};    // 8B

Alignment: the part that changes the final answer

When people get sizeof questions wrong, alignment is usually the reason.

Three rules are enough to explain most layouts.

Rule 1: each member must start at a properly aligned offset

The first member starts at offset 0B. Every member after that must begin at an offset that is a multiple of its own type size.

For example, since an int occupies 4 bytes, its starting address must be a multiple of 4.

Example:

class A {
    const int _a;    // 4B
    char _b;         // 1B
    const short _c;  // 2B
    double _d;       // 8B
};

Memory layout:

+----------+-----------+-------+----------+------------+
| _a (4B)  | _b (1B)   |       | _c (2B)  | _d (8B)    |
+----------+-----------+-------+----------+------------+
0          4           5       6          8            16

Notice the padding between _b and _c, and again before _d.

Rule 2: if a class contains another class object, that subobject must also be properly aligned

If class B contains an object of class A, then the starting position of that A subobject inside B must satisfy the alignment requirement of A.

More specifically, its start offset must be a multiple of the size requirement implied by the largest member inside A.

Example:

class A {
    const int _a;    // 4B
    char _b;         // 1B
    const short _c;  // 2B
    double _d;       // 8B
};

class B : public A {
    int _e;          // 4B
    class A _class_a;// 16B ; 必须以 8B 的整数倍作为起始位置(A 类包含的 double 类型变量 _d 为最大元素,大小为 8B)
};

Memory layout:

+----------+-----------+-------+----------+------------+----------+-------+----------------+
| _a (4B)  | _b (1B)   |       | _c (2B)  | _d (8B)    | _e (4B)  |       | _class_a (16B) |
+----------+-----------+-------+----------+------------+----------+-------+----------------+
0          4           5       6          8            16         20      24               40

Here, _class_a cannot start at offset 20; it must begin at an offset that is a multiple of 8, so it starts at 24.

Rule 3: the total size of the class must be a multiple of its largest alignment requirement

After all members are laid out, the final class size must still be rounded up so that it is a multiple of the largest member alignment inside the class.

Example:

class A {
    const int _a;    // 4B
    char _b;         // 1B
    const short _c;  // 2B
    double _d;       // 8B
};

class B : public A {
    int _e;          // 4B
    class A _class_a;// 16B ; 必须以 8B 的整数倍作为起始位置(A 类包含的 double 类型变量 _d 为最大元素,大小为 8B)
    char _f;         // 1B ; 类 B 的总大小必须是 8B 的整数倍(继承了 A 类的成员变量,其中 double _d 为最大元素,大小为 8B)
};

Memory layout:

+----------+-----------+-------+----------+------------+----------+-------+----------------+---------+-----------+
| _a (4B)  | _b (1B)   |       | _c (2B)  | _d (8B)    | _e (4B)  |       | _class_a (16B) | _f (1B) |           |
+----------+-----------+-------+----------+------------+----------+-------+----------------+---------+-----------+
0          4           5       6          8            16         20      24               40        41          48

Even though _f ends at 41, the total size of B becomes 48, not 41, because the class must be padded to a multiple of 8.

A note on inheritance and overridden virtual functions

In an inheritance hierarchy, if the base class contains virtual functions, the derived class will also have a corresponding vtable pointer.

However, overriding a virtual function in the derived class does not by itself increase the object size.

class A {
public:
    const int _a;    // 4B
    char _b;         // 1B
    const short _c;  // 2B
    double _d;       // 8B
    virtual void vfunc() {};// 8B
};
// sizeof(A) = 24B

class B : public A {
public:
    virtual void vfunc() {};// 8B
};
// sizeof(B) = 24B (重写父类虚函数不会增加子类的内存大小)

One more detail is worth noting: in testing, class A places the vtable pointer at the very beginning of the object, and only then lays out the remaining members. This can be checked with the offsetof macro from the cstddef header.

In the example above, _a starts at offset 8, which implies that the vtable pointer associated with vfunc() starts at offset 0.

Memory layout of A:

+----------+----------+-----------+-------+----------+------------+
| vfunc*   | _a (4B)  | _b (1B)   |       | _c (2B)  | _d (8B)    |
+----------+----------+-----------+-------+----------+------------+
0          8          12          13      14         16           24

Why an empty class is not size 0

An empty class is a special case.

class C {};          // sizeof(C) = 1B
class D : public C {};// sizeof(D) = 1B ; 不计算空父类的大小
class E : public C {
    int _a;
};
// sizeof(E) = 4B ; 不计算空父类的大小

Even though class C {} has no data members at all, its size is not 0B; it is 1B.

At the same time, when another class inherits from an empty base class, that 1B from the empty base is not counted toward the derived class size.

So sizeof(D) is still 1B, and sizeof(E) is 4B, not 5B.

In practice, when estimating sizeof for a class, the safest order is:

  1. count only the non-static data members,
  2. add the vtable pointer if there are virtual functions,
  3. apply alignment rules to member offsets,
  4. finally round the total size up to the required alignment.

That process explains most class-size questions clearly, including the ones that look tricky at first glance.