A format specifier tells printf how to turn a value into text, and scanf how to turn text into a value. It is the one part of C where the compiler cannot help you by default - the argument types are invisible to the function - so this page is the table to check against.
The general shape of a specification is:
%[flags][width][.precision][length]conversion
Only the % and the conversion letter are required. %-8.2lf uses all five parts.
The Conversion Table
| Specifier | Meaning | printf argument | scanf argument |
|---|---|---|---|
%d | signed decimal integer | int | int * |
%i | signed integer | int (same as %d) | int * (also accepts 0x, 0-octal) |
%u | unsigned decimal | unsigned int | unsigned int * |
%f | decimal floating point | double | float * |
%F | as %f, uppercase INF/NAN | double | - |
%e / %E | scientific notation | double | float * |
%g / %G | shorter of %f / %e | double | float * |
%a / %A | hexadecimal floating point | double | float * |
%c | single character | int (promoted char) | char * |
%s | string | char * ('\0'-terminated) | char * (buffer) |
%p | pointer address | void * | void ** |
%x / %X | unsigned hexadecimal | unsigned int | unsigned int * |
%o | unsigned octal | unsigned int | unsigned int * |
%n | store chars written so far | int * | int * |
%% | a literal % | none | matches a literal % |
%[...] | scanset (scanf only) | - | char * |
Two entries deserve a warning. %n writes to memory rather than printing, and is the mechanism behind the format-string vulnerability - never let user text reach a format string. And %p requires a void *; passing an int * directly is undefined even though every real compiler prints the address anyway.
Length Modifiers
The length modifier goes between the precision and the conversion letter, and it says how big the argument actually is.
Signed conversions (d i) and unsigned ones (u x o) take different types - %lu wants an unsigned long, not a long, and getting that wrong is undefined behavior, not a cosmetic slip.
| Modifier | With d i (printf / scanf) | With u x o (printf / scanf) |
|---|---|---|
hh | int (from signed char) / signed char * | unsigned int (from unsigned char) / unsigned char * |
h | int (from short) / short * | unsigned int (from unsigned short) / unsigned short * |
| (none) | int / int * | unsigned int / unsigned int * |
l | long / long * | unsigned long / unsigned long * |
ll | long long / long long * | unsigned long long / unsigned long long * |
z | signed size type / pointer to it | size_t / size_t * |
j | intmax_t / intmax_t * | uintmax_t / uintmax_t * |
t | ptrdiff_t / ptrdiff_t * | unsigned counterpart of ptrdiff_t |
And for the floating-point and character families:
| Modifier | With | printf type | scanf type |
|---|---|---|---|
| (none) | f e g a | double | float * |
l | f e g a | double (accepted, no effect) | double * |
L | f e g a | long double | long double * |
l | c s | wint_t / wchar_t * | wchar_t * |
The combinations you actually type:
%zu is the one to memorize. sizeof, strlen, and every standard-library size returns size_t, which is 8 bytes on a 64-bit system while int is 4. printf("%d", strlen(s)) is a real bug that appears to work on some platforms - use %zu.
The hh and h modifiers exist mainly for scanf, where the destination size matters. In printf a short is promoted to int before the call, so %d would print it correctly anyway; %hd merely documents the intent.
Fixed-Width Types
For <stdint.h> types like int32_t, the portable specifiers are macros from <inttypes.h>:
PRId32 expands to the right letters for your platform, and adjacent string literals concatenate, which is why the format is split into three pieces. It is ugly; the alternative - casting to long long and using %lld - is often more readable and always correct.
Flags
Flags go immediately after the % and change the presentation. They apply to printf only.
| Flag | Effect | Example | Output |
|---|---|---|---|
- | pad after the value instead of before | %-6d| on 42 | 42 | |
+ | always print a sign for signed values | %+d on 42 | +42 |
| space | print a space where a + would go | % d on 42 | 42 |
0 | pad with zeros rather than spaces | %06d on 42 | 000042 |
# | alternate form: 0x for %x, 0 for %o, keep the point for %g | %#x on 255 | 0xff |
Flags can combine: %-+8.2f is minimum width 8, two decimals, always signed, padded after the number. - beats 0 if you write both.
Width and Precision
Width is a minimum field size - the output is padded to reach it, and never truncated to fit it. Precision (after a dot) means something different for each family:
| Conversion | Precision means |
|---|---|
%f %e | digits after the decimal point (default 6) |
%g | total significant digits (default 6) |
%s | maximum characters to print |
%d %i %u %x %o | minimum digits, zero-padded |
A * in place of either number takes it from an int argument, read before the value itself. That is how you build tables whose column widths are computed at runtime.
In scanf the width has a different and much more important job: it limits how much input a conversion will consume, which is the only thing standing between %s and a buffer overflow.
Where printf and scanf Differ
They look alike and disagree in four places. Each of these is a live bug source:
| Situation | printf | scanf |
|---|---|---|
double value | %f (float promoted to double) | %lf - %f means float * |
| Width | minimum field size, pads | maximum input consumed, truncates |
| Arguments | values | pointers - &x, or an array name |
| A space in the format | prints a space | skips any run of whitespace |
Try 2.5 hello. The format reading it and the format printing it use different letters for the same double, and the 15 in %15s caps the input at 15 characters plus the terminator - exactly the size of word.
The scanf Scanset
%[...] reads a set of characters rather than a whitespace-delimited word. It is the way to read a whole line with scanf, though fgets remains the better tool:
[^\n] means "any characters except a newline". %[0-9] would read only digits, %[abc] only those three letters. Unlike %s, a scanset does not skip leading whitespace, so a stray newline in the buffer makes it match nothing at all.
Getting It Wrong Is Undefined Behavior
printf cannot see your argument's real type. It reads whatever number of bytes the specifier implies, interpreted however the specifier says. So a mismatch is not a formatting glitch - it is reading memory as the wrong thing:
printf("%d\n", 3.5); /* prints garbage: reads a double's bits as int */
printf("%s\n", 42); /* treats 42 as an address - crash */
printf("%d\n", strlen(s)); /* size_t read as int - wrong on 64-bit */
printf("%f\n", 3); /* int read as double - garbage */
The defense costs one flag. Compile with -Wall and GCC checks every literal format string against its arguments:
gcc -Wall -Wextra program.c -o program
It reports each of the four lines above at compile time. This is the single highest-value warning in C, and it is on by default in the editor above.
For the practical side of using these - aligned tables, printing floats readably, reading input without the classic newline bug - see printf and scanf.
Frequently Asked Questions
What does %d mean in C?
%d is the conversion specifier for a signed decimal integer - an int. In printf it prints the argument as base-10 digits; in scanf it reads base-10 digits into an int *. %i means the same thing in printf, but in scanf %i also accepts 0x hex and leading-zero octal.
What is the difference between %f and %lf in C?
In printf there is no practical difference: float arguments are promoted to double, so %f prints both, and C99 accepts %lf as a synonym. In scanf the difference is critical - %f writes into a float * (4 bytes) and %lf into a double * (8 bytes). Using %f for a double corrupts memory.
How do I print a size_t or a long long in C?
%zu for size_t (what sizeof and strlen return) and %lld / %llu for long long / unsigned long long. Using %d for these is undefined behavior - it happens to work on 32-bit systems and misbehaves on 64-bit ones, which is why the bug survives so long.
What happens if you use the wrong format specifier in C?
It is undefined behavior, not a rounding error. printf reads the argument as whatever the specifier claims, so printf("%d", 3.5) prints garbage and printf("%s", 42) follows the number 42 as an address and usually crashes. Compile with -Wall, which makes GCC check format strings against arguments.