sizeof, alignof, alignas
sizeof returns byte sizes, alignof queries alignment requirements, alignas enforces alignment — the toolkit for precise memory layout control in C++.
sizeofsince C++98sizeof is a compile-time operator returning the byte size of a type or expression; alignof (C++11) returns its alignment requirement; alignas (C++11) over-aligns a type or variable to a stricter boundary.
Overview
Every object has two independent layout properties: size (how many bytes it occupies) and alignment (the address granularity it requires). sizeof and alignof observe these properties at compile time; alignas enforces them. All three leave zero runtime trace.
This vocabulary is essential for SIMD kernels, network serialization, lock-free data structures, and cache-friendly containers.
sizeof Operator
sizeof never evaluates its operand — side effects are silently suppressed. The return type is std::size_t, an unsigned type from <cstddef>.
int i = 0;
sizeof(i++); // 4; i remains 0 — increment never executes
// Applied to a type, parentheses are required.
// Applied to an expression, they are optional.
sizeof(int) // 4
sizeof i // 4 — identicalFundamental types (typical 64-bit platform):
sizeof(char) // 1 — always 1 by the standard's definition
sizeof(short) // 2
sizeof(int) // 4
sizeof(long) // 4 (MSVC/Windows LLP64), 8 (GCC/Clang LP64)
sizeof(long long) // 8
sizeof(float) // 4
sizeof(double) // 8
sizeof(long double) // 8 (MSVC), 12 or 16 (GCC/Clang x86)
sizeof(void*) // 8 (64-bit), 4 (32-bit)
sizeof(std::nullptr_t) // implementation-defined; == sizeof(void*) on all common platforms // C++11Arrays, strings, and containers:
int arr[10];
sizeof(arr) // 40
sizeof(arr) / sizeof(arr[0]) // 10 — portable element-count idiom
sizeof("hello") // 6 — char[6], null terminator included
sizeof('\n') // 1 — char literal; sizeof(wchar_t) for L'\n'
sizeof(L"hi") // sizeof(wchar_t)*3: 6 on Windows (2-byte wchar_t), 12 on Linux (4-byte)
// std::vector's sizeof is the fixed object size, not the heap allocation
std::vector<int> v{1, 2, 3};
sizeof(v) // 24 (three pointer-/size-fields) — NOT v.size() * 4Parameter packs (C++11):
template<typename... Ts>
constexpr std::size_t type_count() { return sizeof...(Ts); }
static_assert(type_count<int, double, char>() == 3);Compile-time assertions:
static_assert(sizeof(float) == 4, "unexpected IEEE 754 layout");
// Safe-cast guard — ensures source fits in destination
template<class To, class From>
To narrow_cast(From v) {
static_assert(sizeof(From) <= sizeof(To));
return static_cast<To>(v);
}sizeof is ill-formed on incomplete types, functions, bit-field members, and void.
Struct Padding and Layout
The compiler inserts padding so each member sits at an address that is a multiple of its alignment requirement. The struct's total size is then rounded up to a multiple of the most-aligned member's alignment so arrays of structs remain aligned.
struct Bad {
char a; // offset 0, size 1
// 3 bytes padding
int b; // offset 4, size 4
char c; // offset 8, size 1
// 3 bytes padding
int d; // offset 12, size 4
}; // sizeof(Bad) == 16
struct Good { // same four fields, sorted largest-to-smallest
int b; // offset 0, size 4
int d; // offset 4, size 4
char a; // offset 8, size 1
char c; // offset 9, size 1
// 2 bytes padding (struct size must be multiple of 4)
}; // sizeof(Good) == 12
#include <cstddef>
static_assert(offsetof(Good, b) == 0);
static_assert(offsetof(Good, a) == 8);offsetof is only well-defined on standard-layout types. Using it on types with virtual functions, virtual bases, or non-trivial constructors is undefined behavior.
alignof Operator (C++11)
alignof(T) returns the minimum byte boundary an object of type T must be placed on. The result is always a power of two and equals the alignment of T's most strictly aligned member.
alignof(char) // 1
alignof(short) // 2
alignof(int) // 4
alignof(double) // 8
alignof(void*) // 8 (64-bit)
struct Widget { int i; double d; };
alignof(Widget) // 8 — dictated by double
sizeof(Widget) // 16 — 4 + 4 padding + 8
// Equivalent type trait
#include <type_traits>
static_assert(std::alignment_of_v<double> == 8); // C++17 _v helper
// Maximum alignment any fundamental type requires on this platform
alignof(std::max_align_t) // typically 16 on x86-64 // C++11alignof accepts only type names, not expressions (unlike sizeof).
alignas Specifier (C++11)
alignas(N) over-aligns a type or variable to at least N bytes. N must be a power of two and must not be less than the type's natural alignment — under-alignment is ill-formed.
// SIMD: AVX/AVX2 aligned loads require 32-byte alignment
alignas(32) float simd_buf[8];
// Prevent false sharing between threads
struct alignas(64) PaddedCounter {
std::atomic<int64_t> value{0};
// struct padded to 64 bytes; next PaddedCounter starts on a fresh cache line
};
// Type-level alignas
struct alignas(16) Vec4 {
float x, y, z, w;
};
static_assert(alignof(Vec4) == 16);
static_assert(sizeof(Vec4) == 16);
// Under-alignment is ill-formed — rejected at compile time
// struct alignas(1) Bad { double d; }; // error: 1 < alignof(double) == 8C++17 aligned operator new: Prior to C++17, ::operator new only guaranteed alignof(std::max_align_t) alignment (typically 16 bytes). Types with stricter requirements required placement new into manually aligned storage. C++17 added overloads taking std::align_val_t, so heap allocation now respects alignas:
auto* v = new Vec4; // guaranteed 16-byte aligned since C++17
// uses operator new(sizeof(Vec4), std::align_val_t{16})Aligned Storage
When you need to reserve storage for a type without constructing it — as in std::optional, std::variant, or object pools — use alignas(T) std::byte[sizeof(T)] (C++17):
#include <cstddef> // std::byte (C++17)
#include <memory> // std::launder (C++17)
template<typename T>
class ManualStorage {
alignas(T) std::byte buf_[sizeof(T)];
bool live_ = false;
public:
template<typename... Args>
T& emplace(Args&&... args) {
if (live_) destroy();
::new(buf_) T(std::forward<Args>(args)...);
live_ = true;
return get();
}
// std::launder required: new object was created in existing storage;
// launder resets pointer provenance so the compiler doesn't alias-optimize it away
T& get() { return *std::launder(reinterpret_cast<T*>(buf_)); } // C++17
void destroy() { if (live_) { get().~T(); live_ = false; } }
~ManualStorage() { destroy(); }
};std::aligned_storage<Size, Align> (C++11) was the original facility but was deprecated in C++23 as error-prone. The alignas(T) std::byte[sizeof(T)] idiom is its replacement.
Hardware Alignment
Cache-line padding eliminates false sharing between threads on separate cache lines:
#include <new> // std::hardware_destructive_interference_size (C++17)
#ifdef __cpp_lib_hardware_interference_size
constexpr std::size_t kCacheLine = std::hardware_destructive_interference_size;
#else
constexpr std::size_t kCacheLine = 64; // safe fallback for x86-64/ARM64
#endif
struct alignas(kCacheLine) ShardedCounter {
std::atomic<int64_t> value{0};
};
// Each element occupies its own cache line — no false sharing
std::vector<ShardedCounter> counters(std::thread::hardware_concurrency());SIMD aligned loads: _mm256_load_ps / _mm512_load_ps fault or silently corrupt on misaligned addresses:
#include <immintrin.h>
alignas(32) float a[8] = {1,2,3,4,5,6,7,8};
alignas(32) float b[8] = {8,7,6,5,4,3,2,1};
__m256 va = _mm256_load_ps(a); // 32-byte-aligned: safe
__m256 vb = _mm256_load_ps(b);
__m256 vc = _mm256_add_ps(va, vb);
// _mm256_loadu_ps accepts unaligned pointers — mildly slower on older µarchsstd::align (C++11) adjusts a pointer to the next aligned address within a caller-managed buffer:
#include <memory>
void* ptr = raw_buf;
std::size_t space = buf_size;
void* aligned = std::align(alignof(T), sizeof(T), ptr, space);
// aligned: first T-aligned address in [ptr, ptr+space); space reduced accordingly
// returns nullptr if the buffer is too smallPacked Structs
Remove padding for network and file formats where exact byte layout is contractual:
#pragma pack(push, 1)
struct EthernetHeader {
uint8_t dst[6];
uint8_t src[6];
uint16_t ethertype;
}; // sizeof == 14, not 16
#pragma pack(pop)
// GCC/Clang attribute form
struct __attribute__((packed)) IPv4Header {
uint8_t version_ihl;
uint8_t dscp_ecn;
uint16_t total_length;
uint16_t identification;
uint16_t flags_fragment;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
uint32_t src_ip;
uint32_t dst_ip;
}; // sizeof == 20Packed pointers may be misaligned. Dereferencing a misaligned multi-byte pointer is undefined behavior on ARM, MIPS, and most non-x86 architectures. Use memcpy to safely read multi-byte fields:
uint32_t read_ip(const IPv4Header* h) {
uint32_t ip;
std::memcpy(&ip, &h->src_ip, sizeof(ip)); // copies bytes; handles misalignment
return ip;
}Best Practices
- Sort struct members largest-to-smallest to reduce padding without compiler pragmas.
- Prefer
alignas(T) std::byte[sizeof(T)]over deprecatedstd::aligned_storage_t(deprecated C++23). - Use
std::hardware_destructive_interference_size(C++17) with a fallback constant rather than hardcoding 64 for cache-line padding. - When using
newwith over-aligned types (alignas(N)where N >alignof(std::max_align_t)), ensure C++17 aligned allocation is active or fall back to placement new. - Always call
std::launderwhen accessing objects through a reinterpreted pointer after placement new.
Common Pitfalls
Array decay in function parameters — sizeof sees a pointer:
void process(int arr[10]) {
sizeof(arr); // 8 (pointer size), not 40 — the array decayed
}
template<std::size_t N>
void process(int (&arr)[N]) {
sizeof(arr); // 4*N — correct
}sizeof on string literals includes the null terminator:
sizeof("hi") // 3, not 2 — char[3]: {'h','i','\0'}
strlen("hi") // 2 — the logical string lengthsizeof a class template reports fixed object size, not logical size:
sizeof(std::string) // 32 (typical SSO layout) — not the character count
sizeof(std::vector<int>) // 24 (three size_t fields)
sizeof(std::unordered_map<int,int>) // 56 — fixed; use .size() for element countsizeof on bit-field members is ill-formed:
struct Flags { uint8_t a : 3; uint8_t b : 5; };
Flags f;
// sizeof(f.a) — ill-formed; bit-field members have no addressable sizePacked struct pointer dereference on non-x86:
#pragma pack(push, 1)
struct Header { uint32_t value; }; // may be placed at an odd address
#pragma pack(pop)
Header h;
uint32_t* p = &h.value; // potentially misaligned
*p = 42; // UB on ARM without hardware unaligned-access supportSee Also
offsetof— compile-time byte offset of a struct member- Struct layout and standard-layout types
std::optional— uses aligned storage internallystd::variant— aligned storage over a discriminated type set- Move semantics and object lifetime — placement new and manual destruction