Why this matters
Almost every program in this track communicates through printf, and every exercise compares your output character for character. Being precise about formatting saves a lot of frustration. Just as important is reading what the compiler tells you. Beginners often see a wall of red text and start changing things at random; experienced C programmers read the first message carefully, fix that, and rebuild. This lesson teaches both skills.
printf: a format string plus values
printf ("print formatted") takes a format string followed by zero or more values. Ordinary characters in the format are printed as they are. Each conversion specification, starting with %, is replaced by the next value, formatted according to its letter:
#include <stdio.h>
int main(void)
{
printf("%d apples and %d pears\n", 3, 5);
printf("pi is about %f\n", 3.14159);
printf("pi is about %.2f\n", 3.14159);
printf("name: %s, initial: %c\n", "Ada", 'A');
printf("100%% done\n");
return 0;
}
3 apples and 5 pears
pi is about 3.141590
pi is about 3.14
name: Ada, initial: A
100% done
The most common specifiers:
| specifier | prints | example value | output |
|---|---|---|---|
%d | an int in decimal | 42 | 42 |
%u | an unsigned int | 42u | 42 |
%x | an unsigned int in hexadecimal | 255 | ff |
%f | a double, 6 decimals by default | 2.5 | 2.500000 |
%e | a double in scientific notation | 1234.5 | 1.234500e+03 |
%g | a double, shortest of %f/%e | 2.5 | 2.5 |
%c | a single character | 'x' | x |
%s | a string | "hi" | hi |
%% | a literal percent sign | (none) | % |
Types like int and double are the subject of the next module. For now: whole numbers are int and print with %d; numbers with a decimal point are double and print with %f.
Warning:
printfcannot check that the values match the specifiers at run time. If you pass adoubleto%d, it prints garbage. gcc checks format strings at compile time and warns (-Wformat, enabled by-Wall), which is one reason to always compile with warnings.
Width, precision and flags
Between % and the letter you can add, in this order:
% [flags] [width] [.precision] letter
- 0 + 10 .2 f
- width: the minimum number of characters. Shorter output is padded with spaces on the left.
- precision: for
%f, digits after the decimal point; for%s, the maximum characters printed. - flags:
-left-aligns (pads on the right),0pads numbers with zeros,+always shows a sign.
printf("[%6d]\n", 42); /* [ 42] right-aligned in 6 */
printf("[%-6d]\n", 42); /* [42 ] left-aligned */
printf("[%06d]\n", 42); /* [000042] zero-padded */
printf("[%+d]\n", 42); /* [+42] */
printf("[%8.3f]\n", 2.5); /* [ 2.500] */
printf("[%.3s]\n", "abcdef"); /* [abc] */
printf("[%2d]\n", 12345); /* [12345] width is a minimum, never truncates */
This is how you line up columns in tables.
Escape sequences
Inside a string literal, a backslash starts an escape sequence, a way to write characters you cannot type directly:
| escape | meaning |
|---|---|
\n | newline |
\t | tab |
\\ | a backslash |
\" | a double quote inside a string |
\' | a single quote (needed in character literals: '\'') |
\0 | the null character, value 0 (important for strings later) |
Note the difference between escapes and %: escapes are handled by the compiler when it reads the string literal; % is handled by printf at run time. That is why a literal percent sign is %%, not \%.
puts and putchar
Two simpler output functions: puts("text") prints a string followed by a newline (no formatting), and putchar('x') prints one character. They are useful when you have nothing to format.
Comments
/* A block comment can span
several lines. */
int main(void)
{
// A line comment runs to the end of the line (since C99).
return 0; /* comments can follow code */
}
The preprocessor replaces each comment with a space before compilation. Good comments explain why code does something; the code itself already says what. Comments are also a quick way to disable code while experimenting.
Errors and warnings
The compiler produces two kinds of diagnostics:
- Errors mean the code is not valid C. No executable is produced.
- Warnings mean the code is valid but suspicious. An executable is produced, and it may well be wrong.
A gcc diagnostic looks like this:
main.c: In function 'main':
main.c:6:5: error: expected ';' before 'prinft'
6 | prinft("That is 100%% fine.\n");
| ^~~~~~
Read it as: file:line:column: kind: message, followed by the source line with a caret under the spot. Warnings end with the name of the option that enabled them, such as [-Wunused-variable], which you can look up.
How to work through a build that fails:
- Fix the first error first. One mistake often causes a cascade of follow-on errors. A missing brace can produce ten messages about lines that are fine.
- Look at the line before if the message says "expected X before Y". The compiler noticed the problem at Y; the mistake is usually just before it.
- Rebuild after each fix. Many of the remaining messages may disappear.
- Treat warnings as bugs until proven otherwise. Common ones:
main.c:5:9: warning: unused variable 'total' [-Wunused-variable]
main.c:6:12: warning: too many arguments for format [-Wformat-extra-args]
main.c:7:14: warning: format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat=]
Professional C projects usually build with -Werror, which turns warnings into errors so they cannot be ignored. You can do the same any time with gcc -std=c17 -Wall -Wextra -Werror main.c.
Common mistakes
- Missing
\nat the end of output. The text appears, but the next output (or the shell prompt) runs onto the same line, and an exercise check fails. - Mismatched specifier and value, such as
printf("%d\n", 2.5);. It prints nonsense, not- Read the
-Wformatwarning.
- Read the
- Fewer values than specifiers, such as
printf("%d %d\n", 1);. The second%dprints whatever happens to be in a register: undefined behavior. gcc warnsformat '%d' expects a matching 'int' argument. - Smart quotes pasted from a web page (
“ ”) instead of". gcc reportsstray '\342' in program, which looks baffling until you know it means "a non-ASCII byte".
Recap
printf(format, values...)prints the format, replacing each%specifier with the next value:%dint,%fdouble,%cchar,%sstring,%%a percent sign.- Width, precision and flags (
%-10s,%7.2f,%03d) control layout. Width is a minimum. - Escape sequences such as
\n,\tand\\are handled by the compiler inside literals. - Comments are
/* ... */(do not nest) and// .... - Errors stop the build; warnings do not but usually point at real bugs. Fix the first error first, rebuild, and use
-Werrorto keep code warning-free.
Going further: man 3 printf documents every flag and conversion. Look for %e, %g, and the * width, which takes the width from an argument: printf("%*d\n", 6, 42);.