C Static Assert (C11)

Compile-time assertions for safer code

🛡️ What is Static Assert?

Static assertions check conditions at compile time, not runtime. They help catch errors early by verifying assumptions about types, sizes, and constants before your program even runs, making your code more reliable and portable.


#include <stdio.h>
#include <assert.h>

// Check that int is at least 32 bits
_Static_assert(sizeof(int) >= 4, "int must be at least 32 bits");

// Verify array size assumptions
#define BUFFER_SIZE 1024
_Static_assert(BUFFER_SIZE > 0, "Buffer size must be positive");

struct Point {
    int x, y;
};

// Ensure struct packing assumptions
_Static_assert(sizeof(struct Point) == 8, "Point struct should be 8 bytes");

int main() {
    printf("All static assertions passed!\n");
    printf("int size: %zu bytes\n", sizeof(int));
    printf("Point size: %zu bytes\n", sizeof(struct Point));
    
    return 0;
}
                                    

Output:

All static assertions passed!
int size: 4 bytes
Point size: 8 bytes

Key Static Assert Concepts

Compile-time

Checks happen during compilation

_Static_assert(sizeof(int) == 4, 
    "Expected 32-bit integers");
🚫

No Runtime Cost

Zero performance impact

// No code generated in final binary
_Static_assert(1 + 1 == 2, 
    "Math still works");
📏

Size Verification

Check type and struct sizes

_Static_assert(sizeof(void*) == 8,
    "Expecting 64-bit pointers");
🔧

Constant Expressions

Works with compile-time constants

#define MAX_USERS 100
_Static_assert(MAX_USERS > 0,
    "Need at least one user");

🔹 Basic Static Assert Usage

Static assertions using static_assert allow developers to verify conditions at compile-time, catching errors before the program runs. This powerful feature ensures that certain assumptions about data types, sizes, and values are correct during compilation rather than at runtime. For example, static_assert(sizeof(int) == 4, "Integer must be 4 bytes"); verifies that integers are indeed four bytes on the target platform. This compile-time checking is particularly valuable for cross-platform development, embedded systems programming, and when working with hardware-specific requirements where data size assumptions are critical for correct program operation.

#include <stdio.h>
#include <stdint.h>

// Verify platform assumptions
_Static_assert(sizeof(char) == 1, "char must be 1 byte");
_Static_assert(sizeof(short) >= 2, "short must be at least 2 bytes");
_Static_assert(sizeof(long) >= 4, "long must be at least 4 bytes");

// Check array bounds
#define MAX_NAME_LENGTH 50
#define MIN_NAME_LENGTH 1
_Static_assert(MAX_NAME_LENGTH > MIN_NAME_LENGTH, 
    "Max length must be greater than min length");

// Verify bit field assumptions
struct Flags {
    unsigned int active : 1;
    unsigned int visible : 1;
    unsigned int reserved : 30;
};
_Static_assert(sizeof(struct Flags) == sizeof(unsigned int),
    "Flags should fit in one integer");

// Check enum values
enum Status {
    STATUS_OK = 0,
    STATUS_ERROR = 1,
    STATUS_PENDING = 2
};
_Static_assert(STATUS_OK == 0, "OK status must be zero");

int main() {
    printf("Platform verification complete!\n");
    printf("All size assumptions are correct.\n");
    
    struct Flags f = {1, 0, 0};
    printf("Flags size: %zu bytes\n", sizeof(f));
    
    return 0;
}

Output:

Platform verification complete!
All size assumptions are correct.
Flags size: 4 bytes

🔹 Practical Applications

Static assertions find invaluable real-world applications in systems programming, embedded development, and cross-platform software projects where compile-time guarantees are essential. They're commonly used to verify struct padding and alignment, ensure bit-field sizes match hardware register specifications, validate template parameters in generic programming, and confirm that endianness assumptions are correct for network protocols. For instance, in embedded systems, static_assert(sizeof(HardwareRegister) == 4, "Register size mismatch"); ensures memory-mapped I/O structures match actual hardware specifications. This prevents subtle bugs that would only manifest during hardware testing, saving significant debugging time and improving code reliability in production environments.

🔸 Network Protocol Structures

#include <stdio.h>
#include <stdint.h>

// Network packet header
struct PacketHeader {
    uint16_t version;
    uint16_t type;
    uint32_t length;
    uint32_t checksum;
} __attribute__((packed));

// Ensure exact size for network compatibility
_Static_assert(sizeof(struct PacketHeader) == 12,
    "PacketHeader must be exactly 12 bytes for network protocol");

// Configuration validation
#define BUFFER_SIZE 4096
#define MAX_CONNECTIONS 1000
#define THREAD_POOL_SIZE 8

_Static_assert(BUFFER_SIZE >= 1024, "Buffer too small for efficient I/O");
_Static_assert(MAX_CONNECTIONS <= 10000, "Too many connections for system");
_Static_assert(THREAD_POOL_SIZE > 0 && THREAD_POOL_SIZE <= 32,
    "Thread pool size must be between 1 and 32");

int main() {
    printf("Network protocol validation passed!\n");
    printf("PacketHeader size: %zu bytes\n", sizeof(struct PacketHeader));
    printf("Configuration is valid.\n");
    
    return 0;
}

🔸 Memory Layout Verification

#include <stdio.h>
#include <stddef.h>

// Critical data structure for embedded system
struct SensorData {
    uint32_t timestamp;     // Offset 0
    uint16_t temperature;   // Offset 4
    uint16_t humidity;      // Offset 6
    uint32_t pressure;      // Offset 8
} __attribute__((packed));

// Verify memory layout for hardware compatibility
_Static_assert(offsetof(struct SensorData, timestamp) == 0,
    "timestamp must be at offset 0");
_Static_assert(offsetof(struct SensorData, temperature) == 4,
    "temperature must be at offset 4");
_Static_assert(offsetof(struct SensorData, humidity) == 6,
    "humidity must be at offset 6");
_Static_assert(offsetof(struct SensorData, pressure) == 8,
    "pressure must be at offset 8");
_Static_assert(sizeof(struct SensorData) == 12,
    "SensorData must be exactly 12 bytes");

// Array size validation
#define SENSOR_HISTORY_SIZE 100
uint32_t sensor_history[SENSOR_HISTORY_SIZE];

_Static_assert(sizeof(sensor_history) == SENSOR_HISTORY_SIZE * sizeof(uint32_t),
    "History array size calculation error");

int main() {
    printf("Memory layout verification successful!\n");
    printf("SensorData size: %zu bytes\n", sizeof(struct SensorData));
    printf("History array size: %zu bytes\n", sizeof(sensor_history));
    
    return 0;
}

🔹 Advanced Techniques

Advanced static assertion techniques enable sophisticated compile-time validation for complex programming scenarios and template metaprogramming. Developers can combine static assertions with type traits, constexpr functions, and template specializations to create powerful compile-time checks. For example, verifying that a class is trivially copyable before using memcpy operations, ensuring template parameters meet specific requirements, or validating that array dimensions match expected values. These techniques are particularly useful in library development where API contracts must be enforced at compile-time. Advanced usage includes checking for proper inheritance hierarchies, validating constexpr computations, and ensuring that custom allocators meet standard allocator requirements for container classes.

✅ Best Practices:

  • Use descriptive messages: Explain why the assertion matters
  • Check platform assumptions: Verify type sizes and alignment
  • Validate constants: Ensure configuration values make sense
  • Verify struct layout: Critical for binary compatibility

⚠️ Limitations:

  • Constant expressions only: Cannot check runtime values
  • C11 required: Not available in older standards
  • Limited scope: Cannot access local variables
// Advanced: Template-like assertions with macros
#define ASSERT_POWER_OF_2(n) \
    _Static_assert(((n) & ((n) - 1)) == 0 && (n) > 0, \
        #n " must be a power of 2")

#define CACHE_LINE_SIZE 64
#define PAGE_SIZE 4096

ASSERT_POWER_OF_2(CACHE_LINE_SIZE);
ASSERT_POWER_OF_2(PAGE_SIZE);

// Conditional assertions
#ifdef DEBUG
    _Static_assert(sizeof(void*) == 8, "Debug build requires 64-bit platform");
#endif

// Version compatibility
#define API_VERSION_MAJOR 2
#define API_VERSION_MINOR 1

_Static_assert(API_VERSION_MAJOR >= 2, "API version too old");
_Static_assert(API_VERSION_MAJOR < 10, "API version too new");

🧠 Test Your Knowledge

When do static assertions get evaluated?