Menu

Format Specifiers in C: The Complete printf and scanf Reference

Every C format specifier in one place - what %d, %s, %c, %f, %p and the rest mean, the length modifiers l, ll, h and z, width, precision and flags, and which ones differ between printf and scanf.

This page includes runnable editors - edit, run, and see output instantly.

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

SpecifierMeaningprintf argumentscanf argument
%dsigned decimal integerintint *
%isigned integerint (same as %d)int * (also accepts 0x, 0-octal)
%uunsigned decimalunsigned intunsigned int *
%fdecimal floating pointdoublefloat *
%Fas %f, uppercase INF/NANdouble-
%e / %Escientific notationdoublefloat *
%g / %Gshorter of %f / %edoublefloat *
%a / %Ahexadecimal floating pointdoublefloat *
%csingle characterint (promoted char)char *
%sstringchar * ('\0'-terminated)char * (buffer)
%ppointer addressvoid *void **
%x / %Xunsigned hexadecimalunsigned intunsigned int *
%ounsigned octalunsigned intunsigned int *
%nstore chars written so farint *int *
%%a literal %nonematches 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.

ModifierWith d i (printf / scanf)With u x o (printf / scanf)
hhint (from signed char) / signed char *unsigned int (from unsigned char) / unsigned char *
hint (from short) / short *unsigned int (from unsigned short) / unsigned short *
(none)int / int *unsigned int / unsigned int *
llong / long *unsigned long / unsigned long *
lllong long / long long *unsigned long long / unsigned long long *
zsigned size type / pointer to itsize_t / size_t *
jintmax_t / intmax_t *uintmax_t / uintmax_t *
tptrdiff_t / ptrdiff_t *unsigned counterpart of ptrdiff_t

And for the floating-point and character families:

ModifierWithprintf typescanf type
(none)f e g adoublefloat *
lf e g adouble (accepted, no effect)double *
Lf e g along doublelong double *
lc swint_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.

FlagEffectExampleOutput
-pad after the value instead of before%-6d| on 4242 |
+always print a sign for signed values%+d on 42+42
spaceprint a space where a + would go% d on 42 42
0pad with zeros rather than spaces%06d on 42000042
#alternate form: 0x for %x, 0 for %o, keep the point for %g%#x on 2550xff

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:

ConversionPrecision means
%f %edigits after the decimal point (default 6)
%gtotal significant digits (default 6)
%smaximum characters to print
%d %i %u %x %ominimum 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:

Situationprintfscanf
double value%f (float promoted to double)%lf - %f means float *
Widthminimum field size, padsmaximum input consumed, truncates
Argumentsvaluespointers - &x, or an array name
A space in the formatprints a spaceskips 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.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED