# Layout of object 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
).
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
struct S {
int x;
char* y;
};
there is a consecutive sequence of sizeof(int)
bytes within an S
object, called a subobject, that contain the value of x
, and another subobject with sizeof(char*)
bytes that contains the value of y
. The two cannot be interleaved.
t1, t2,...tN
, the size must be at least sizeof(t1) + sizeof(t2) + ... + sizeof(tN)
given the preceding points. However, depending on the alignment (opens new window) 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 of `n` which is larger than the size of all members & base classes. Each member `memN` will typically be placed at an address which is a multiple of `alignof(memN)`, and `n` will typically be the largest `alignof` out of all members' `alignof`s. Due to this, if a member with a smaller `alignof` is followed by a member with a larger `alignof`, 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 larger `alignof` is followed by a member with a smaller `alignof`, 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))
// 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 (opens new window), 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.