Skip to content
C++
Language
since C++98
Basic

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++98

sizeof 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>.

cpp
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 — identical

Fundamental types (typical 64-bit platform):

cpp
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++11

Arrays, strings, and containers:

cpp
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() * 4

Parameter packs (C++11):

cpp
template<typename... Ts>
constexpr std::size_t type_count() { return sizeof...(Ts); }

static_assert(type_count<int, double, char>() == 3);

Compile-time assertions:

cpp
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.

cpp
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.

cpp
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++11

alignof 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.

cpp
// 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) == 8

C++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:

cpp
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):

cpp
#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:

cpp
#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:

cpp
#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 µarchs

std::align (C++11) adjusts a pointer to the next aligned address within a caller-managed buffer:

cpp
#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 small

Packed Structs

Remove padding for network and file formats where exact byte layout is contractual:

cpp
#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 == 20

Packed 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:

cpp
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 deprecated std::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 new with 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::launder when accessing objects through a reinterpreted pointer after placement new.

Common Pitfalls

Array decay in function parameters — sizeof sees a pointer:

cpp
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:

cpp
sizeof("hi")    // 3, not 2 — char[3]: {'h','i','\0'}
strlen("hi")    // 2 — the logical string length

sizeof a class template reports fixed object size, not logical size:

cpp
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 count

sizeof on bit-field members is ill-formed:

cpp
struct Flags { uint8_t a : 3; uint8_t b : 5; };
Flags f;
// sizeof(f.a) — ill-formed; bit-field members have no addressable size

Packed struct pointer dereference on non-x86:

cpp
#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 support

See Also