Menu

printf in C: Format Strings, Specifiers, and Printing Floats

How printf actually works in C - the format string model, the specifiers you use daily, width and precision for aligned output, printing floats sensibly, the return value, and why printf(user_input) is a security hole.

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

printf is the first function every C programmer learns and the one most of them never fully read the manual for. It is worth ten minutes, because its format string is a small language of its own - and because getting a specifier wrong is not a typo, it is undefined behavior.

It lives in <stdio.h>, so every program using it starts with #include <stdio.h>.

The Format String Model

printf walks the format string one character at a time. Anything that is not a % is printed exactly as written. A % begins a conversion specification, which eats the next argument and prints it in the requested form.

The arguments are matched to the specifiers strictly by position: the first % takes count, the second takes name, and so on. printf has no way to check that you passed what you promised - it cannot see the types - so the format string is a contract you must keep. That is what makes -Wall worth turning on: GCC reads the literal format string at compile time and warns when an argument does not match.

Nothing forces a newline. printf("Hello") leaves the cursor after the o, which is why almost every format string ends with \n.

The Specifiers You Use Daily

Five cover most code:

SpecifierPrintsExample callOutput
%da signed intprintf("%d", -42)-42
%fa doubleprintf("%f", 2.5)2.500000
%sa string (char *)printf("%s", "hi")hi
%ca single characterprintf("%c", 'A')A
%pa pointer addressprintf("%p", (void*)&x)0x7ffd...

Two rules hide in that snippet. A literal % is written %% - a lone % at the end of a format string is undefined behavior. And %p requires a void *, so cast the pointer; passing an int * directly is technically undefined even though it prints fine in practice.

The full table - every specifier, every length modifier, the scanf column - is on the format specifiers page. This page stays with the ones you reach for.

Width, Precision, and Alignment

Between the % and the letter you can put flags, a width, and a precision. That is how you turn a loop into a table.

The width is a minimum, never a maximum: %3d with the value 123456 prints all six digits. The precision means different things per type - digits after the decimal point for %f, maximum characters for %s, minimum digits for %d.

Both can be supplied at runtime with a *, which reads an int argument:

Changing width re-lays the whole table. This is the standard way to print aligned columns in C - no string-padding helper needed.

Printing Floats Sensibly

%f with no precision always prints six digits after the point, which is rarely what anyone wants:

Three choices, and a rule of thumb for each:

  • %.Nf for money, measurements, anything with a natural number of decimals. %.2f is what you want for currency.
  • %e for scientific notation, always d.dddddde±dd.
  • %g when you do not know the magnitude in advance. It picks %f or %e whichever is shorter and strips trailing zeros - the sanest default for logging a value you have not seen.

One trap worth stating plainly: there is no %lf in printf. float arguments are promoted to double before printf ever sees them, so %f handles both, and %lf is only meaningful in scanf where the size of the destination matters. (C99 does accept %lf in printf as a synonym, but the habit of writing it leads people to write %f in scanf, which genuinely breaks.)

Integers of Other Widths

%d is for int. Larger or unsigned types need a length modifier, and mismatching them is undefined behavior rather than a rounding error:

%zu for size_t is the one people most often get wrong - sizeof returns size_t, not int, so printf("%d", sizeof x) is a bug that happens to work on 32-bit systems and misbehaves on 64-bit ones.

The Return Value

printf returns the number of characters it wrote, or a negative number on failure. It is usually ignored, and mostly that is fine - but it is genuinely useful when you need to know how wide the output was:

The related sprintf and snprintf write into a buffer instead of the screen, and return the length they produced. Always prefer snprintf, which takes the buffer size and cannot overflow:

snprintf returns the length the full string would have needed, so a return value at or above the buffer size tells you the output was truncated.

The Format-String Vulnerability

This is the one security issue in printf, and it is severe. Never pass data you did not write as the format string:

/* DANGEROUS - never do this */
printf(user_input);

/* Correct */
printf("%s", user_input);

If user_input contains %s, printf reads an argument that was never passed and follows whatever pointer-shaped garbage it finds, usually crashing. %x repeated dumps stack contents to the screen, which may include passwords or keys. And %n, which writes the character count to an argument, historically turned this into arbitrary memory corruption and remote code execution.

The rule is absolute and costs nothing: the format string is always a literal you wrote. Anything variable goes in as an argument. GCC's -Wformat-security flags the mistake, and it is included in -Wall -Wextra.

Buffering: Why Output Sometimes Appears Late

printf does not write to the terminal immediately. Output is buffered - flushed when the buffer fills, when a newline appears on a terminal, or when the program exits normally. Two consequences:

  • A program that crashes may lose output that "already printed". If you are debugging with printf and the last message never appears, the crash happened after that line, not before. Add fflush(stdout) or send debug output to stderr, which is unbuffered.
  • fprintf(stderr, ...) is the right way to print errors. It is unbuffered, and it goes to a separate stream that users can redirect independently of normal output.

Frequently Asked Questions

How does printf work in C?

printf takes a format string and scans it character by character. Ordinary characters are printed as-is; each % starts a conversion specification that consumes one of the following arguments and prints it in the requested form. printf("%d apples\n", 5) prints 5 apples and a newline.

How do I print a float with 2 decimal places in C?

Use a precision: printf("%.2f\n", 3.14159) prints 3.14. The number after the dot is how many digits follow the decimal point. %f with no precision always prints 6 digits, which is why unformatted floats look so noisy.

Why does printf print garbage or crash?

Almost always a mismatch between the specifier and the argument. printf("%d", 3.5) or printf("%s", 42) is undefined behavior - printf cannot see the real types, it trusts the format string. Compile with -Wall, which makes GCC check the format against the arguments.

Why is printf(user_input) dangerous?

If the user's text contains % sequences, printf will obey them and read arguments that were never passed - leaking stack memory, and with %n even writing to memory. This is the classic format-string vulnerability. Always write printf("%s", user_input) instead.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED