C / TYPES AND REPRESENTATION
sizeof and the size of every type
Use sizeof correctly on types, expressions, arrays and structs, print it with %zu, and know why an array parameter loses its size.
What you will learn
- Print sizeof results with %zu, since the result type is size_t, not int.
- Read sizeof as a question about the operand's type, not its value or contents.
- Get element counts with sizeof arr / sizeof arr[0], only where arr is a real array.
- Expect sizeof(struct) to be larger than the sum of its members because of padding.
Understanding sizeof and the size of every type
sizeof is an operator, not a library function, and it answers exactly one question: how many bytes of storage an object of the given type occupies. The unit is fixed by the standard, since sizeof(char) is 1, so every other size is a multiple of a char and no valid type can measure 0. The result has type size_t, an unsigned type wide enough to describe any object, which is why printf needs the %zu conversion instead of %d.
What sizeof inspects is the type of its operand, never the value stored there. sizeof x where x is a double is 8 whether x holds 0.0 or 1e300, and sizeof p where p is a char * is the size of the pointer itself, not of the string it addresses. The same rule explains the two different array results below: an array's length lives in its type, so sizeof nums covers the whole array only where the compiler still sees int[10]. A parameter written int a[] has been adjusted to int *, and the 10 is gone for good.
The compiler computes sizeof while translating, so the operand is never executed: sizeof(i++) produces a number without ever incrementing i, and sizeof f() needs only f's return type, not a call. There are two spellings, a type name requiring parentheses as in sizeof(int) and an expression that may drop them as in sizeof n, and both yield a constant usable as an array dimension. The one exception is a variable-length array, whose size is computed at run time. Structures show the payoff of this mental model: because each member must land on an address its type can be aligned to, sizeof of a struct is the sum of its members plus padding, and reordering members can change it.
<stdio.h>
/* Sizes below come from gcc on x86-64 Linux (LP64). */
int main(void)
{
int n = 5;
printf("sizeof(char) = %zu\n", sizeof(char));
printf("sizeof(short) = %zu\n", sizeof(short));
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof(long) = %zu\n", sizeof(long));
printf("sizeof(long long) = %zu\n", sizeof(long long));
printf("sizeof(float) = %zu\n", sizeof(float));
printf("sizeof(double) = %zu\n", sizeof(double));
printf("sizeof(void *) = %zu\n", sizeof(void *));
printf("sizeof(size_t) = %zu\n", sizeof(size_t));
printf("n = %d, sizeof n = %zu\n", n, sizeof n);
printf("sizeof 'a' = %zu\n", sizeof 'a');
return 0;
}
sizeof is a compile-time question about a type, namely how many chars of storage an object of that type occupies including padding, not a question about a value or about what a pointer points at.
Worked examples
Arrays keep their size, parameters do not
The same array measured in the scope where it was declared and inside a function that receives it.
<stdio.h>
void print_size(int a[])
{
printf("inside function: %zu\n", sizeof a);
}
int main(void)
{
int nums[10];
printf("in main: %zu\n", sizeof nums);
printf("elements: %zu\n", sizeof nums / sizeof nums[0]);
print_size(nums);
return 0;
}
Example explained
Line 1int nums[10]; gives nums the type int[10], so sizeof nums is 10 * 4 = 40 bytes of storage.
Line 2sizeof nums / sizeof nums[0] divides 40 by 4; sizeof binds tighter than /, so no extra parentheses are needed.
Line 3The parameter written int a[] is really int *, so sizeof a is the size of a pointer, 8 here, and the 10 is unrecoverable.
Line 4That is why C functions taking an array almost always take a separate length argument.
The operand is never executed
Side effects written inside sizeof do not happen, because the compiler only needs the operand's type.
<stdio.h>
int calls = 0;
int bump(void)
{
calls++;
return 42;
}
int main(void)
{
int i = 5;
size_t s1 = sizeof bump();
size_t s2 = sizeof(i++);
printf("s1 = %zu, s2 = %zu\n", s1, s2);
printf("calls = %d, i = %d\n", calls, i);
return 0;
}
Example explained
Line 1sizeof bump() is 4 because bump returns int; the function is not called, so calls stays 0.
Line 2sizeof(i++) is also 4, and i is still 5 afterwards because the increment is part of an operand that never runs.
Line 3Both sizeof expressions became the literal constant 4 during compilation, so nothing inside them reached execution.
Line 4The only operand that is evaluated is a variable-length array, whose size is not known until run time.
A struct is bigger than its members
Padding inserted for alignment makes sizeof of a struct depend on the order the members are declared in.
<stdio.h>
struct wide { char c; int n; char d; };
struct tight { int n; char c; char d; };
int main(void)
{
printf("char + int + char = %zu\n",
sizeof(char) + sizeof(int) + sizeof(char));
printf("sizeof(struct wide) = %zu\n", sizeof(struct wide));
printf("sizeof(struct tight) = %zu\n", sizeof(struct tight));
return 0;
}
Example explained
Line 1The members alone account for 1 + 4 + 1 = 6 bytes, but the int must start at an offset that is a multiple of 4.
Line 2struct wide therefore wastes 3 bytes after c and 3 more at the end, so that arrays of it keep every member aligned: 12 bytes.
Line 3struct tight declares the int first, so the two chars fill offsets 4 and 5 and only 2 tail bytes are padding: 8 bytes.
Line 4Nothing about the stored data changed between the two structs, only the declaration order.
Important notes
sizeof(char) is 1 by definition, but that byte is CHAR_BIT bits, at least 8 and 8 on every mainstream platform; sizeof never reports bit counts, so multiply by CHAR_BIT if you need bits.
sizeof cannot appear in an #if directive, because the preprocessor runs before types exist; use _Static_assert(sizeof(int) == 4, "unexpected int width"); for a compile-time check instead.
Common mistakes
Applying sizeof to a pointer to measure what it points at: char buf[sizeof s] inside void f(char *s) reserves 8 bytes on x86-64 regardless of the string length, so the following copy truncates or overruns.
Writing malloc(sizeof(p)) instead of malloc(sizeof *p): that allocates room for one pointer, and writing the full struct through p then corrupts memory past the block.
Printing with printf("%d", sizeof x): size_t is 8 bytes here while %d expects 4, which is undefined behaviour rather than a harmless mismatch.
Try it yourself
Change, predict, then run
Declare struct rec { char tag; long value; char flag; };, write down your prediction for sizeof(struct rec), then print it next to sizeof(char) + sizeof(long) + sizeof(char). Reorder the three members to make the struct as small as possible and print the new size.
Open the C workspaceCheck your understanding
Given int a[6]; int *p = a; on a system where int is 4 bytes and pointers are 8, what does sizeof p / sizeof p[0] evaluate to?
- 6, because p points at a six-element array
- 2, because sizeof p is the size of the pointer, not of the array
- 24, the total number of bytes in the array
- 1, because sizeof p and sizeof p[0] describe the same object
Show answer
sizeof p asks about the type of p, which is int *, giving 8; sizeof p[0] is sizeof(int), giving 4, so the quotient is 2. The 6 is tempting because p really does point at six ints, but that count exists only in the type int[6], and p does not have that type; the division would only yield 6 if written on a in a scope where a is still declared as an array.