Skip to content
C++
Language
since C++11
Intermediate

alignas

Controls the alignment requirement of a type or variable declaration, overriding the compiler's default to meet hardware, ABI, or concurrency constraints.

alignassince C++11

A declaration specifier that sets the alignment requirement of a type or variable to a specified power-of-two boundary, which must be at least as strict as its natural alignment.

Overview

Every type has a natural alignment β€” the byte boundary to which the CPU requires (or prefers) its objects to be addressed. alignas lets you strengthen that requirement, forcing a larger boundary than the compiler would choose by default. It cannot be used to weaken alignment; attempting to do so makes the program ill-formed.

Alignment control matters in several concrete scenarios:

  • SIMD and vectorisation: SSE/AVX load instructions require 16- or 32-byte aligned data. Misaligned loads produce a fault on strict-alignment ISAs or a measurable performance penalty on x86.
  • Cache-line isolation: Two frequently written fields sharing a cache line causes false sharing between threads. Writes from one thread invalidate the entire line for other cores, forcing spurious reloads even when threads never access the same variable.
  • Overaligned storage buffers: Reserving raw bytes for placement new requires the buffer's alignment to match the target type's.
  • Hardware-mapped regions: Some peripheral or DMA registers mandate specific alignment for correct bus transactions.

In C++17, ::operator new gained overloads for over-aligned types, so dynamically allocated over-aligned objects are handled correctly without manual allocator wiring. In C++14 and earlier, standard allocation only guarantees alignof(std::max_align_t) β€” typically 16 bytes.

Syntax

cpp
alignas(expression)  // integral constant; 0 or a valid power-of-2 alignment
alignas(type-id)     // equivalent to alignas(alignof(type-id))
alignas(pack...)     // C++11: expands to one alignas per pack element; takes the max

alignas is permitted on:

  • A class or struct definition.
  • A non-bitfield data member declaration.
  • A variable declaration.

It is not permitted on a function parameter or the exception parameter of a catch clause.

When multiple alignas specifiers appear on the same declaration, the effective alignment is the largest non-zero value among them. alignas(0) is always silently ignored.

Weakening natural alignment is ill-formed:

cpp
struct alignas(8) S {};
struct alignas(1) U { S s; }; // error: alignas(1) would weaken S's alignment

Examples

Aligning a struct for SIMD

cpp
#include <cassert>

struct alignas(32) Vec8f {   // 32-byte boundary required for AVX _mm256_load_ps
    float data[8];
};

static_assert(alignof(Vec8f) == 32);
static_assert(sizeof(Vec8f)  == 32);

void process(const Vec8f* v) {
    // Safe to use _mm256_load_ps(v->data) β€” alignment guaranteed by type
    (void)v;
}

Aligning individual variables

alignas on a variable overrides alignment for that object only, independent of the type's natural alignment:

cpp
#include <cstdint>

alignas(64) int hot_counter = 0;          // occupies its own cache line
alignas(alignof(double)) char scratch[8]; // usable as double-aligned storage

// alignof reports the type's alignment, not the variable's.
// hot_counter has type int (alignof == 4), but &hot_counter is 64-byte aligned.
static_assert(alignof(int) == 4);

Eliminating false sharing (C++17)

cpp
#include <new>  // std::hardware_destructive_interference_size β€” C++17

struct ThreadCounters {
    alignas(std::hardware_destructive_interference_size) long writer_count{0};
    alignas(std::hardware_destructive_interference_size) long reader_count{0};
};

Without alignas, both fields likely share a 64-byte cache line. A write from the writer thread marks the entire line dirty, stalling the reader thread on its next access β€” even though neither thread ever touches the other's variable.

When targeting C++14 or earlier where the constant is unavailable, use a conservative fixed value:

cpp
constexpr std::size_t k_cache_line = 64; // portable conservative assumption

struct ThreadCounters {
    alignas(k_cache_line) long writer_count{0};
    alignas(k_cache_line) long reader_count{0};
};

Aligned storage for placement new

cpp
#include <new>      // std::launder β€” C++17
#include <utility>

template <typename T>
class InlineOptional {
    alignas(T) unsigned char storage_[sizeof(T)];
    bool engaged_ = false;

public:
    template <typename... Args>
    void emplace(Args&&... args) {
        ::new (storage_) T(std::forward<Args>(args)...);
        engaged_ = true;
    }

    T& value() {
        // std::launder required in C++17+ after placement new through a char buffer
        return *std::launder(reinterpret_cast<T*>(storage_));
    }

    ~InlineOptional() {
        if (engaged_) value().~T();
    }
};

alignas(T) guarantees storage_ carries alignof(T) alignment, so the placement new produces a correctly aligned object. Without it the buffer has alignment 1 β€” undefined behaviour for any type with stricter requirements.

Parameter pack expansion

alignas accepts a type or non-type parameter pack, expanding to one specifier per element with the maximum winning:

cpp
#include <algorithm>

template <typename... Ts>
struct AlignedUnion {
    alignas(Ts...) unsigned char storage[std::max({sizeof(Ts)...})];
};

using U = AlignedUnion<int, double, char>;
static_assert(alignof(U) == alignof(double)); // double has the strictest alignment

Best Practices

Prefer alignas(T) over hard-coded numbers when matching a type's alignment. If T gains a wider member in a later revision, the specifier stays correct automatically.

Use std::hardware_destructive_interference_size for cache-line separation (C++17). The constant is target-specific; hard-coding 64 is incorrect on architectures with larger lines and may be unnecessarily wasteful on those with smaller ones.

Audit dynamic allocation in pre-C++17 code. In C++14, std::make_unique<Vec8f>() invokes the non-aligned operator new and may return memory that is insufficiently aligned. Either upgrade to C++17 or use aligned_alloc / platform allocators directly.

Verify actual address alignment at runtime during debugging, not via alignof. alignof reports the type's requirement; inspecting reinterpret_cast<uintptr_t>(ptr) % alignment == 0 confirms the live address.

Common Pitfalls

Attempting to weaken alignment. Specifying a smaller alignment than a member or base class requires is ill-formed. The diagnostic may cite an opaque ABI constraint rather than identifying the offending member, making the error non-obvious in deeply nested structures.

alignof does not reflect alignas on a variable.

cpp
alignas(64) int x;
static_assert(alignof(decltype(x)) == 4); // true: alignof sees the type, not the var
// runtime address of x is 64-byte aligned despite the above

Code that reads alignof(T) to decide whether a cast is safe may draw the wrong conclusion when the storage was declared with a stricter alignas.

alignas on function parameters is ignored or ill-formed. The standard does not permit it; some compilers accept it silently and ignore it, others diagnose it. Pass over-aligned objects by pointer or reference:

cpp
void bad(alignas(32) Vec8f v);   // ill-formed; compiler may not preserve alignment
void good(const Vec8f& v);       // reference; alignment of the caller's object is kept

Over-aligned types in std::vector before C++17. std::allocator<T> before C++17 calls the unaligned operator new, so std::vector<Vec8f> may contain misaligned elements. In C++17 this is fixed automatically. In earlier standards, supply a custom allocator that calls aligned_alloc.

See Also