Proven C Book←↑→

Appendix B — printf and scanf formats in full

Chapter 23 learned the minimal set (%d, %s, %%) and chapter 66 dissected the format string. This appendix is a place for looking things up — read only the line you need. One rule goes first: if the format and the argument’s type go out of step it is outside the contract (undefined behaviour) (chapters 48 and 66).

The skeletons of a format specification#

Output and input have similar syntax but different meanings. First the two skeletons side by side.

output (the printf family)input (the scanf family)
% [flags] [width] [.precision] [length] conversion% [*] [max width] [length] conversion
width = the minimum number of characters — it fills if short and does not cut if overwidth = the maximum number of characters to read — the means of protecting the buffer
there is a precision (.)there is no precision
* takes the width or precision as an argument* reads but does not store (suppression)

Table 105.1 — The skeleton of output and input conversions

Output conversions — the whole list#

conversionargument typeoutput formnote
%d %iinta decimal signed integerin output the two are the same
%uunsigned inta decimal unsigned integer
%b %Bunsigned intbinary — %#b puts 0b in frontadded in C23
%ounsigned intoctalattach # and a 0 goes in front
%x %Xunsigned inthexadecimal lower and upper caseattach # and 0x/0X goes in front
%f %Fdouble123.456000default precision 6
%e %Edouble1.234560e+02exponential notation
%g %Gdoubleautomatically the shorterit removes trailing zeros
%a %Adouble0x1.edp+6hexadecimal floating point (C99) — the bits seen exactly
%cint (as a character)one characterthe argument comes promoted to int
%schar *the string up to the NULthe maximum length can be limited by precision
%pvoid *implementation-defined notationusually a hexadecimal address
%nint *(no output)★ it writes the number of characters printed — not used, for security
%%—one % character

Table 105.2 — The full list of output conversions

Flags, width, precision#

placenotationmeaning
flag-left alignment (the default is right)
0fill the spare places with zeros (ignored together with -)
+attach a sign even to positives
spaceone space before positives
#alternative form — %#b→0b, %#o→0, %#x→0x, %#f→always a decimal point
width%8dat least 8 places
%*dtake the width as an argument: printf("%*d", 8, n)
precision%.3fto three decimal places (rounded)
%.5dan integer to at least 5 digits (zeros in front)
%.4sa string to at most 4 characters
%.*sthe maximum length as an argument: printf("%.*s", len, p)

Table 105.3 — The places of flags, width and precision

%.*s is a form used often in this book — because a string that carries its length separately (chapters 63 and 95′s views) can be printed as it is, without NUL termination.

One thing must be kept, though. The width or precision argument that * takes is of type int. Pass a length carried as a size_t (as views mostly do) straight in and the variadic argument’s type goes out of step — the very mismatch seen in chapter 63. The canonical form checks the length and then casts.

if (v.size <= INT_MAX)
    printf("%.*s", (int)v.size, (const char *)v.ptr);

Length modifiers — the place that tells the type’s width#

Type information does not ride along into variadic arguments (chapter 63), so this letter is itself the contract. Get it wrong and the stack is read wrongly.

modifierconversions used withoutput argumentinput argument
(none)d i u o x Xint / unsigned intint * / unsigned *
hhd i u o x Xint (interpreted at char width)signed char *
hd i u o x Xint (interpreted at short width)short *
ld i u o x Xlonglong *
lld i u o x Xlong longlong long *
jd i u o x Xintmax_tintmax_t *
zd i u o x Xsize_tsize_t *
td i u o x Xptrdiff_tptrdiff_t *
Lf e g along doublelong double *
lc / swint_t / wchar_t *wchar_t *

Table 105.4 — The length modifiers

Reals need particular care. In output a float becomes a double by the default promotion, so %f alone suffices and %lf means the same. In input there is no promotion, so %f must be float * and %lf double * without fail (chapter 66′s misconception box).

Formats for fixed-width integers#

For a type such as int32_t the real type differs by platform, so writing the format by hand can go out of step. The standard put macros in <inttypes.h>.

#include <inttypes.h>

uint64_t total = 1234567890123ULL;
printf("total = %" PRIu64 "\n", total);   /* it is string concatenation */

int32_t n = 0;
sscanf(line, "%" SCNd32, &n);

The naming rule is simple — for output PRI + conversion + width (PRId32, PRIu64, PRIx16), for input SCN + conversion + width (SCNd32, SCNu64). Besides 8, 16, 32, 64, the width place also takes MAX (PRIdMAX) and PTR (PRIxPTR).

Input conversions — the whole list#

conversionwhat it readsargumentnote
%da decimal integerint *it skips leading whitespace
%ian integer (base automatic)int *0x means hex, a leading 0 octal
%uunsigned decimalunsigned *it accepts a minus sign too and wraps
%ba binary integerunsigned *added in C23
%o %xoctal, hexadecimalunsigned *
%f %e %ga realfloat *★ for double it is %lf
%cthe character as it ischar *★ it does not skip whitespace. give a width and it reads that many
%sup to whitespacechar[]★ always give a maximum width (%63s)
%[...]characters belonging to a setchar[]%[^,] is characters that are not a comma. it reads whitespace too
%ppointer notationvoid **only what was printed with %p is read back
%n(reads nothing)int *it writes the number of characters consumed so far
%%the % character—

Table 105.5 — The full list of input conversions

Insert the suppression character * and it reads without storing — sscanf(s, "%*d %d", &n) throws away the first number and takes only the second. A suppressed item is not counted in the return value either.

The set specifier is close to a small tool of its own. %[abc] gathers only a, b and c; %[^,\n] gathers characters that are neither a comma nor a newline. Range notation (%[a-z]) is widely used but not guaranteed by the standard.

Rules that hold in input alone#

The return values organised#

functionon successfailure and boundaries
printf fprintfthe number of characters printednegative
sprintfthe number of characters written (excluding the NUL)negative
snprintfthe number of characters that would have been needednegative. if the return value is at least the buffer size it was truncated
scanf sscanfthe number of items filled0 (matching or conversion failure) or EOF
fgetsthe buffer pointernull (end of file or an error)
fputs putsa non-negative valueEOF

Table 105.6 — The return values of the formatted I/O functions

snprintf’s return value rule matters especially — being the number of characters needed, not the number written, the truncation check is need >= (int)sizeof buf (chapter 90).

A collection of common mistakes#

wrong codewhat happensthe mend
printf("%d", sz) — a size_tthe width goes out of step on 64-bit%zu
printf("%s", 42)it reads an integer as an address — collapse%d
printf(user)the format string vulnerability (chapter 66)printf("%s", user)
scanf("%d", n)a value was passed, not an address&n
scanf("%s", buf)it writes past the boundaryspecify a width, as in %63s
scanf("%f", &d) — a doubleonly half is filled%lf
scanf("%c", &c) in successionit reads the leftover newline" %c"
ignoring the return valuefailure passes by quietlycheck the item count
using %nit becomes a passage for memory writesdo not use it

Table 105.7 — Common formatting mistakes and their fixes