C++

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, then p + 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 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.

  • If a class type has members and/or base classes with types t1, t2,...tN, the size must be at least sizeof(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 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’ alignofs. 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))
  • 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 ints. 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.


This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow