Layout of object types
Remarks#
See also Size of integral types.
Class types
By “class”, we mean a type that was defined using the class
or struct
keyword (but not enum struct
or enum class
).
-
Even an empty class still occupies at least one byte of storage; it will therefore consist purely of padding. This ensures that if
p
points to an object of an empty class, thenp + 1
is a distinct address and points to a distinct object. However, it is possible for an empty class to have a size of 0 when used as a base class. See empty base optimisation.class Empty_1 {}; // sizeof(Empty_1) == 1 class Empty_2 {}; // sizeof(Empty_2) == 1 class Derived : Empty_1 {}; // sizeof(Derived) == 1 class DoubleDerived : Empty_1, Empty_2 {}; // sizeof(DoubleDerived) == 1 class Holder { Empty_1 e; }; // sizeof(Holder) == 1 class DoubleHolder { Empty_1 e1; Empty_2 e2; }; // sizeof(DoubleHolder) == 2 class DerivedHolder : Empty_1 { Empty_1 e; }; // sizeof(DerivedHolder) == 2
-
The object representation of a class type contains the object representations of the base class and non-static member types. Therefore, for example, in the following class:
struct S { int x; char* y; };
there is a consecutive sequence of
sizeof(int)
bytes within anS
object, called a subobject, that contain the value ofx
, and another subobject withsizeof(char*)
bytes that contains the value ofy
. The two cannot be interleaved. -
If a class type has members and/or base classes with types
t1, t2,...tN
, the size must be at leastsizeof(t1) + sizeof(t2) + ... + sizeof(tN)
given the preceding points. However, depending on the alignment requirements of the members and base classes, the compiler may be forced to insert padding between subobjects, or at the beginning or end of the complete object.struct AnInt { int i; }; // sizeof(AnInt) == sizeof(int) // Assuming a typical 32- or 64-bit system, sizeof(AnInt) == 4 (4). struct TwoInts { int i, j; }; // sizeof(TwoInts) >= 2 * sizeof(int) // Assuming a typical 32- or 64-bit system, sizeof(TwoInts) == 8 (4 + 4). struct IntAndChar { int i; char c; }; // sizeof(IntAndChar) >= sizeof(int) + sizeof(char) // Assuming a typical 32- or 64-bit system, sizeof(IntAndChar) == 8 (4 + 1 + padding). struct AnIntDerived : AnInt { long long l; }; // sizeof(AnIntDerived) >= sizeof(AnInt) + sizeof(long long) // Assuming a typical 32- or 64-bit system, sizeof(AnIntDerived) == 16 (4 + padding + 8).
-
If padding is inserted in an object due to alignment requirements, the size will be greater than the sum of the sizes of the members and base classes. With
n
-byte alignment, size will typically be the smallest multiple ofn
which is larger than the size of all members & base classes. Each membermemN
will typically be placed at an address which is a multiple ofalignof(memN)
, andn
will typically be the largestalignof
out of all members’alignof
s. Due to this, if a member with a smalleralignof
is followed by a member with a largeralignof
, there is a possibility that the latter member will not be aligned properly if placed immediately after the former. In this case, padding (also known as an alignment member ) will be placed between the two members, such that the latter member can have its desired alignment. Conversely, if a member with a largeralignof
is followed by a member with a smalleralignof
, no padding will usually be necessary. This process is also known as “packing”.
Due to classes typically sharing the alignof
of their member with the largest alignof
, classes will typically be aligned to the alignof
of the largest built-in type they directly or indirectly contain.
// Assume sizeof(short) == 2, sizeof(int) == 4, and sizeof(long long) == 8.
// Assume 4-byte alignment is specified to the compiler.
struct Char { char c; };
// sizeof(Char) == 1 (sizeof(char))
struct Int { int i; };
// sizeof(Int) == 4 (sizeof(int))
struct CharInt { char c; int i; };
// sizeof(CharInt) == 8 (1 (char) + 3 (padding) + 4 (int))
struct ShortIntCharInt { short s; int i; char c; int j; };
// sizeof(ShortIntCharInt) == 16 (2 (short) + 2 (padding) + 4 (int) + 1 (char) +
// 3 (padding) + 4 (int))
struct ShortIntCharCharInt { short s; int i; char c; char d; int j; };
// sizeof(ShortIntCharCharInt) == 16 (2 (short) + 2 (padding) + 4 (int) + 1 (char) +
// 1 (char) + 2 (padding) + 4 (int))
struct ShortCharShortInt { short s; char c; short t; int i; };
// sizeof(ShortCharShortInt) == 12 (2 (short) + 1 (char) + 1 (padding) + 2 (short) +
// 2 (padding) + 4 (int))
struct IntLLInt { int i; long long l; int j; };
// sizeof(IntLLInt) == 16 (4 (int) + 8 (long long) + 4 (int))
// If packing isn't explicitly specified, most compilers will pack this as
// 8-byte alignment, such that:
// sizeof(IntLLInt) == 24 (4 (int) + 4 (padding) + 8 (long long) +
// 4 (int) + 4 (padding))
// Assume sizeof(bool) == 1, sizeof(ShortIntCharInt) == 16, and sizeof(IntLLInt) == 24.
// Assume default alignment: alignof(ShortIntCharInt) == 4, alignof(IntLLInt) == 8.
struct ShortChar3ArrShortInt {
short s;
char c3[3];
short t;
int i;
};
// ShortChar3ArrShortInt has 4-byte alignment: alignof(int) >= alignof(char) &&
// alignof(int) >= alignof(short)
// sizeof(ShortChar3ArrShortInt) == 12 (2 (short) + 3 (char[3]) + 1 (padding) +
// 2 (short) + 4 (int))
// Note that t is placed at alignment of 2, not 4. alignof(short) == 2.
struct Large_1 {
ShortIntCharInt sici;
bool b;
ShortIntCharInt tjdj;
};
// Large_1 has 4-byte alignment.
// alignof(ShortIntCharInt) == alignof(int) == 4
// alignof(b) == 1
// Therefore, alignof(Large_1) == 4.
// sizeof(Large_1) == 36 (16 (ShortIntCharInt) + 1 (bool) + 3 (padding) +
// 16 (ShortIntCharInt))
struct Large_2 {
IntLLInt illi;
float f;
IntLLInt jmmj;
};
// Large_2 has 8-byte alignment.
// alignof(IntLLInt) == alignof(long long) == 8
// alignof(float) == 4
// Therefore, alignof(Large_2) == 8.
// sizeof(Large_2) == 56 (24 (IntLLInt) + 4 (float) + 4 (padding) + 24 (IntLLInt))
-
If strict alignment is forced with
alignas
, padding will be used to force the type to meet the specified alignment, even when it would otherwise be smaller. For example, with the definition below,Chars<5>
will have three (or possibly more) padding bytes inserted at the end so that its total size is 8. It is not possible for a class with an alignment of 4 to have a size of 5 because it would be impossible to make an array of that class, so the size must be “rounded up” to a multiple of 4 by inserting padding bytes.// This type shall always be aligned to a multiple of 4. Padding shall be inserted as // needed. // Chars<1>..Chars<4> are 4 bytes, Chars<5>..Chars<8> are 8 bytes, etc. template<size_t SZ> struct alignas(4) Chars { char arr[SZ]; }; static_assert(sizeof(Chars<1>) == sizeof(Chars<4>), "Alignment is strict.\n");
-
If two non-static members of a class have the same access specifier, then the one that comes later in declaration order is guaranteed to come later in the object representation. But if two non-static members have different access specifiers, their relative order within the object is unspecified.
-
It is unspecified what order the base class subobjects appear in within an object, whether they occur consecutively, and whether they appear before, after, or between member subobjects.
Arithmetic types
Narrow character types
The unsigned char
type uses all bits to represent a binary number. Therefore, for example, if unsigned char
is 8 bits long, then the 256 possible bit patterns of a char
object represent the 256 different values {0, 1, …, 255}. The number 42 is guaranteed to be represented by the bit pattern 00101010
.
The signed char
type has no padding bits, i.e., if signed char
is 8 bits long, then it has 8 bits of capacity to represent a number.
Note that these guarantees do not apply to types other than narrow character types.
Integer types
The unsigned integer types use a pure binary system, but may contain padding bits. For example, it is possible (though unlikely) for unsigned int
to be 64 bits long but only be capable of storing integers between 0 and 232 - 1, inclusive. The other 32 bits would be padding bits, which should not be written to directly.
The signed integer types use a binary system with a sign bit and possibly padding bits. Values that belong to the common range of a signed integer type and the corresponding unsigned integer type have the same representation. For example, if the bit pattern 0001010010101011
of an unsigned short
object represents the value 5291
, then it also represents the value 5291
when interpreted as a short
object.
It is implementation-defined whether a two’s complement, one’s complement, or sign-magnitude representation is used, since all three systems satisfy the requirement in the previous paragraph.
Floating point types
The value representation of floating point types is implementation-defined. Most commonly, the float
and double
types conform to IEEE 754 and are 32 and 64 bits long (so, for example, float
would have 23 bits of precision which would follow 8 exponent bits and 1 sign bit). However, the standard does not guarantee anything. Floating point types often have “trap representations”, which cause errors when they are used in calculations.
Arrays
An array type has no padding in between its elements. Therefore, an array with element type T
is just a sequence of T
objects laid out in memory, in order.
A multidimensional array is an array of arrays, and the above applies recursively. For example, if we have the declaration
int a[5][3];
then a
is an array of 5 arrays of 3 int
s. Therefore, a[0]
, which consists of the three elements a[0][0]
, a[0][1]
, a[0][2]
, is laid out in memory before a[1]
, which consists of a[1][0]
, a[1][1]
, and a[1][2]
. This is called row major order.