C Format Specifier Cheat Sheet for printf and scanf

printf and scanf trust the format string: if a conversion does not match its argument, the behaviour is undefined even when the output looks right. Choose a C type below to see the exact conversion for each function, the GCC -Wformat warning a wrong choice produces, and a short program you can run.

printf passes promoted values while scanf passes addresses: int, double and size_t specifiers

The one rule to remember

printf receives values after default argument promotion, so float arrives as double and char or short arrive as int. scanf receives pointers, which are never promoted, so its conversion must describe the exact object type: %f for float *, %lf for double *.

Interactive printf and scanf specifier selector

Pick printf or scanf and a C type, try any conversion, and see whether GCC's -Wformat check accepts it. printf receives promoted values (float becomes double); scanf receives addresses, so float * needs %f and double * needs %lf.

printf("%f", value);

Correct match

printf takes double with %f; %lf is accepted since C99.

Recommended
%f
Passed as
double
-Wformat preview · GCC, x86-64 Linux
$ gcc -std=c17 -Wall -Wextra main.c -o main
(no -Wformat warning)

Safe printf snippet for double

#include <stdio.h>

int main(void) {
    double pi = 3.14159265358979;
    printf("pi = %.5f\n", pi);
    return 0;
}

stdout: pi = 3.14159

Length modifiers and width/precision explorer
Modifierprintf argumentscanf target
hhsigned/unsigned char (value promoted to int)signed char * / unsigned char *
hshort (value promoted to int)short * / unsigned short *
(none)int, unsigned int, double (float promoted)int *, unsigned int *, float *
llong, unsigned long; no effect on %flong *, unsigned long *, double * (%lf)
lllong long, unsigned long longlong long *, unsigned long long *
zsize_tsize_t *
tptrdiff_tptrdiff_t *
jintmax_t, uintmax_tintmax_t *, uintmax_t *
Llong doublelong double *
FormatArgumentOutputMeaning
[%5d]42[ 42]Minimum width 5, right-aligned.
[%-5d]42[42 ]- flag left-aligns.
[%05d]42[00042]0 flag pads with zeros.
[%.2f]3.14159[3.14]Precision: 2 digits after the point.
[%8.3f]3.14159[ 3.142]Width 8, precision 3 (rounded).
[%.3s]"format"[for]Precision on %s caps characters printed.
[%+d]42[+42]+ flag always prints the sign.
[%#x]255[0xff]# flag adds the 0x prefix.

For scanf, a width is a maximum: %63s reads at most 63 characters into char buf[64], leaving room for the terminating NUL.

printf vs scanf: type promotion reference

Recommended conversions for 64-bit Linux (LP64). The PRI and SCN macros from <inttypes.h> expand to the right conversion on every platform.

C typeprintf receivesprintfscanfscanf argument
intint%d%d&count
unsigned intunsigned int%u%u&mask
longlong%ld%ld&offset
unsigned longunsigned long%lu%lu&ticks
long longlong long%lld%lld&big
size_t (<stddef.h>)size_t%zu%zu&bytes
ptrdiff_t (<stddef.h>)ptrdiff_t%td%td&gap
floatdouble%f%f&ratio
doubledouble%f%lf&pi
long doublelong double%Lf%Lf&third
charint%c%c&answer
char * (string)char *%s%63sname
void * (pointer)void *%p%p&where
int32_t (<inttypes.h>)int32_t (int)PRId32SCNd32&level
uint64_t (<inttypes.h>)uint64_t (unsigned long)PRIu64SCNu64&hash

Seeing format '%d' expects argument of type 'int'? The format expects argument of type error guide walks through the fix, and scanf vs fgets explains safer line input. Some conversions such as %zu and %lld need C99 or later; check them in the C standard compatibility checker.

Ready to try it? Open the online C compiler, paste a program and run it, or read the C error guides when a build fails.

Frequently asked questions

Why does printf use %f for both float and double?

printf is variadic, so a float argument undergoes default argument promotion to double. %f and %lf (accepted since C99) both print a double. scanf receives pointers, which are never promoted, so it needs %f for float * and %lf for double *.

Which format specifier prints a size_t value?

Use %zu, added in C99. size_t is unsigned long on 64-bit Linux but unsigned long long on 64-bit Windows, so %lu or %llu is not portable.

Why does scanf need & for an int but not for a char array?

scanf stores into objects, so it needs their addresses: &count for an int. An array name such as buf already converts to a pointer to its first element, so scanf("%63s", buf) needs no &. Give %s a width one less than the buffer size.

What does -Wformat check?

When the format string is a literal, GCC and Clang compare each conversion with the type of its argument; -Wall enables -Wformat. A mismatch is undefined behaviour even when the program appears to print the right value.