Stack, heap, alignment and sizeof
sizeof answers a question about the type, not the value: how many bytes one object occupies. For a struct the answer is more than the sum of its members, because the compiler inserts padding to keep every member aligned.
The layout is part of the ABI, not of the standard, so it is fixed for a compiler and platform and may differ on another. This lesson measures the layout here: sizes, padding, the order of members, alignment and the two places objects live.
The sizes of the fundamental types
The program prints the size of the arithmetic types and of a pointer on this machine. The numbers are the ones the ABI fixes: char 1, short 2, int 4, long 8, double 8 and a pointer 8.
The standard only guarantees a minimum range for each type, not these sizes. A program that needs a fixed width uses int32_t and its family from <cstdint>, which lesson 007 introduced.
sizeof is a compile-time constant, so a static_assert can check it. The expression is not evaluated, which is why sizeof(value++) does not increment anything.
#include <iostream>
int main()
{
std::cout << sizeof(char) << ' ' << sizeof(short) << ' ' << sizeof(int) << ' '
<< sizeof(long) << ' ' << sizeof(double) << ' ' << sizeof(void*) << '\n';
}
g++ -std=c++23 -Wall -Wextra sizeof.cpp -o sizeof_demo
./sizeof_demo
1 2 4 8 8 8
Padding
A struct with a char, an int and a char is 12 bytes, not 6. The int must start at an address divisible by 4, so three bytes of padding follow the first char, and the struct's size is a multiple of its alignment.
The program prints the size and the offset of each member, which shows where the padding sits. The offset of the int is 4 and of the second char 8, and the last three bytes are the tail padding.
Padding is invisible to the code and part of every array of the struct, which is why a million small structs pay for it. Reordering the members is often the cheapest way to shrink them.
#include <cstddef>
#include <iostream>
struct mixed
{
char first;
int number;
char last;
};
int main()
{
std::cout << sizeof(mixed) << ' ' << offsetof(mixed, first) << ' '
<< offsetof(mixed, number) << ' ' << offsetof(mixed, last) << '\n';
}
g++ -std=c++23 -Wall -Wextra padding.cpp -o padding
./padding
12 0 4 8
The order of members
The same three members in a different order take 8 bytes: the two chars sit together and only two bytes of padding follow them. Nothing about the values changed, only the declaration.
The program prints the size and the offsets of the reordered struct. The int is first, so the chars fit in its tail, and the alignment of the struct is the alignment of the int.
The standard does not reorder members, so the layout is what the source says. A struct that is written to a file or sent over a wire should not rely on this order across compilers, which is why serialisation writes fields one by one.
#include <cstddef>
#include <iostream>
struct packed
{
int number;
char first;
char last;
};
int main()
{
std::cout << sizeof(packed) << ' ' << offsetof(packed, number) << ' '
<< offsetof(packed, first) << ' ' << offsetof(packed, last) << '\n';
}
g++ -std=c++23 -Wall -Wextra order.cpp -o order_layout
./order_layout
8 0 4 5
Alignment
alignof answers the alignment of a type, and alignas asks for more. A struct declared with alignas(16) starts at a multiple of 16 and takes 16 bytes, even though its member is an int.
The program prints the size and the alignment of the aligned block, the plain int and the struct itself. The block is 16 and 16, the int is 4 and 4.
Alignment matters for the SIMD types and for a buffer that must start on a cache line. Over-alignment is a promise the allocator has to keep, and new has done it since C++17.
#include <iostream>
struct alignas(16) block
{
int value;
};
int main()
{
std::cout << sizeof(block) << ' ' << alignof(block) << ' ' << alignof(int) << '\n';
}
g++ -std=c++23 -Wall -Wextra alignment.cpp -o alignment
./alignment
16 16 4
Stack and heap
A local object lives on the stack, and a new object lives on the heap. The two are different regions of the address space, and the program can show that the addresses differ without printing them.
The program compares the address of a local with the address of a heap object and with the address of the pointer variable itself. All three are different, and the comparisons print 1.
The stack is freed automatically when the function returns, and the heap only by delete or a smart pointer. That lifetime is the real difference; the speed of the access is a consequence and not a guarantee.
#include <iostream>
int main()
{
int local = 1;
int* heap = new int{2};
std::cout << (static_cast<void*>(heap) != static_cast<void*>(&local)) << ' '
<< (static_cast<void*>(&heap) != static_cast<void*>(&local)) << '\n';
delete heap;
}
g++ -std=c++23 -Wall -Wextra places.cpp -o places
./places
1 1
Exercises
- Print sizeof and alignof for the structs of this lesson and for a struct with a double and a char.
- Reorder the members of mixed to make it 8 bytes and check the offsets.
- Add a static_assert that sizeof(int) == 4 and see whether it holds on this machine.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does
sizeofreturn?Answer
The size of the type or object in bytes
sizeof is the size in chars, which are one byte each by definition.
-
What is
sizeof(char)on every conforming C++ implementation?Answer
1
The standard defines char as one byte, and every other size is a multiple of it.
-
What does this program print?
struct mixed { char a; int b; char c; }; std::cout << sizeof(mixed);Answer
12
Three bytes of padding after the first char, and the struct is padded to a multiple of four.
-
What does
offsetof(mixed, b)return when b is an int after a char?Answer
4
The int must start on an address divisible by four, so it sits at offset 4.
-
What does this reordered struct print for sizeof?
struct packed { int n; char a; char b; };Answer
8
The two chars share the tail of the int, and two bytes of padding bring the size to 8.
-
What does
alignof(int)return on this build?Answer
4
The alignment of an int is its size here, four bytes.
-
What does
struct alignas(16) block { int value; };make sizeof(block)?Answer
16
The requested alignment becomes the alignment of the type, and the size is a multiple of it.
-
Which is true of two local variables in the same function?
Answer
Their address order is not guaranteed
The compiler may lay out the stack as it likes, so comparing addresses of unrelated locals is not meaningful.
-
Why is a struct of a char, an int and a char 12 bytes rather than 6?
Answer
Padding keeps each member aligned and the size a multiple of the alignment
Alignment inserts bytes between members and after the last one.
-
What does
sizeof(value++)do to value?Answer
Nothing, the operand is not evaluated
sizeof is a compile-time operator; its operand is not evaluated unless it is a variable-length array, which C++ does not have.
The code on this page was built with g++ 15.2.0 and run on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev