Proven C Book←↑→

Appendix B — printf and scanf formats in full

Chapter 23 taught the minimal set (%d, %s, %%) and chapter 76 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 do not match, it is outside the contract (undefined behaviour) (chapters 48 and 76).

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 — shorter output is padded, longer output is not cutwidth = 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 115.1 — The skeleton of output and input conversion specifications

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 %Gdouble%f or %e style depending on the exponent (%e when it is below −4 or at least the precision)it 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 — avoid it for security
%%—one % character

Table 115.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 115.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 67 and 105′s views) can be printed as it is, without NUL termination.

One rule has to be observed, 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 no longer matches — the very mismatch seen in chapter 67. 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 67), so this length-modifier letter is itself the contract. Get it wrong and an argument is read from the wrong place as the wrong type (undefined behaviour).

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 115.4 — The length modifiers

Formats for the real floating types (float, double) need particular care. In output a float becomes a double by the default argument promotions, so %f alone suffices and %lf means the same. In input there is no promotion, so %f must take a float * and %lf a double *, without fail (chapter 76′s misconception box).

Formats for fixed-width integers#

For a type such as int32_t the underlying type differs by platform, so writing the format by hand can produce a mismatch. 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[]%[^,] matches 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 115.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 apply only to input#

Return values at a glance#

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 115.6 — The return values of the formatted and line I/O functions

Since snprintf’s return value is the number of characters needed, not the number written, the truncation check is need >= (int)sizeof buf, where need holds the return value (chapter 100).

A collection of common mistakes#

wrong codewhat happensthe fix
printf("%d", sz) — a size_tthe width does not match on 64-bit%zu
printf("%s", 42)it reads an integer as an address — usually a crash%d
printf(user)the format string vulnerability (chapter 76)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 115.7 — Common formatting mistakes and their fixes