A strong answer
In C, printf(const char *format, ...) is a variadic function. For the fixed parameter format, the compiler knows the exact type (const char *) and checks it. For the ... arguments, C passes the raw values in CPU registers or on the call stack without any runtime type tags. At runtime, printf has no way to inspect the actual types of the arguments passed to it; it blindly trusts the % specifiers inside the format string to decide which register bank to read (integer/pointer registers vs floating-point XMM registers on x86-64) and how many bytes to consume (4 bytes vs 8 bytes).
If you pass an int (42) with %s, printf reads the integer value 42, interprets it as a 64-bit memory address (0x2a), and tries to dereference it to print a null-terminated string—immediately triggering a segmentation fault. If you pass a double with %d on x86-64 System V ABI, printf reads from the next general-purpose integer register instead of the floating-point XMM register where the double was actually placed, printing unrelated register garbage and shifting the argument cursor for every subsequent % specifier in the call.
What the interviewer is listening for
- Explains that C variadic functions (
...) strip static type information at runtime and rely entirely on the format string to decode registers and stack slots. - Mentions hardware calling convention details (such as integer registers vs floating-point registers, or 32-bit vs 64-bit widths).
- Notes that
-Wall -Wformatallowsgccandclangto statically check literal format strings against the argument list at compile time.
Common mistakes
- Assuming
printf("%f", 42)automatically converts theint42to42.0the way a regular non-variadic function parametervoid foo(double x)does. - Passing a user-controlled string directly as the first argument
printf(buf)instead ofprintf("%s", buf), which allows an attacker to inject%xor%nformat specifiers to read or write stack memory. - Using
%dinstead of%zuforsize_tor%ld/%lldfor 64-bit integers.