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, andlongtypes are defined as 32 bits (4 bytes). - LP64 systems:
longand pointer types are defined as 64 bits (8 bytes), whileintremains 32 bits (4 bytes). - LLP64 systems:
long longand pointer types are 64 bits (8 bytes), whilelongandintare 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:
- Iterates sequentially through each member's declared type.
- Inserts invisible padding bytes before members that do not align naturally with their required offsets.
- 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.