How the C Sizeof Operator Works at Compile Time

In the C programming language, the sizeof operator is a fundamental tool that evaluates the memory footprint of types and expressions directly during the compilation phase. This article explores the internal mechanics of sizeof, explaining how the compiler determines memory sizes using type metadata, platform-specific Application Binary Interfaces (ABIs), and struct padding rules without executing runtime code, along with the single exception involving variable-length arrays.

Static Type Analysis

The C compiler processes sizeof entirely during semantic analysis rather than at runtime. When the compiler encounters sizeof(expression) or sizeof(type), it analyzes the type signature without emitting instructions to evaluate the expression itself. For example, in an expression like sizeof(x++), the increment operation is never converted into executable machine code. The compiler checks the type of x in its symbol table, determines the number of bytes required by that type, and immediately substitutes the entire sizeof expression with an integer constant representing the byte count.

Architecture and Data Models

The numerical value returned by sizeof is determined by the target architecture's Data Model and ABI specifications. Because the C standard only guarantees minimum size relationships (such as sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long)), the compiler relies on built-in configuration tables for the target platform. For instance:

  • ILP32 systems: Pointers, int, and long types are defined as 32 bits (4 bytes).
  • LP64 systems: long and pointer types are defined as 64 bits (8 bytes), while int remains 32 bits (4 bytes).
  • LLP64 systems: long long and pointer types are 64 bits (8 bytes), while long and int are 32 bits (4 bytes).

Because the compilation target is defined prior to code generation, the compiler knows the exact widths of all primitive types and pointer addresses statically.

Structure Layout and Alignment Padding

Calculating the footprint of aggregate types (struct and union) requires the compiler to calculate memory alignment constraints alongside raw member sizes. Hardware architectures read and write memory more efficiently when data addresses are aligned to multiples of the data size (e.g., a 4-byte integer aligned to a 4-byte boundary).

When evaluating sizeof(struct Example), the compiler performs the following steps:

  1. Iterates sequentially through each member's declared type.
  2. Inserts invisible padding bytes before members that do not align naturally with their required offsets.
  3. Appends trailing padding bytes at the end of the structure so that in an array of structures, subsequent elements maintain proper alignment.

For union types, the compiler calculates the footprint by identifying the largest individual member and adding trailing padding, if necessary, to ensure the union satisfies the strictest alignment requirement among its fields.

Array Footprint Calculations

For standard fixed-size arrays, the compiler computes the memory footprint using a direct mathematical formula: the declared number of elements multiplied by the size of the underlying type. Because both the array bound and the element type must be known at compile time, expressions like sizeof(int[10]) resolve immediately to 10 * sizeof(int).

The Runtime Exception: Variable-Length Arrays

Introduced in C99, Variable-Length Arrays (VLAs) represent the only situation where sizeof does not resolve at compile time. When sizeof operates on an array whose bounds are defined by a runtime variable, such as int arr[n], the compiler cannot determine the allocation size ahead of time. In this case, the compiler generates machine instructions that read the value of n at runtime, multiply it by sizeof(int), and produce the result dynamically. In all other scenarios involving fixed-size types and variables, sizeof remains purely a compile-time constant.