Foundations: from source to running program · about 45 min

printf, comments, and reading compiler messages

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:

specifierprintsexample valueoutput
%dan int in decimal4242
%uan unsigned int42u42
%xan unsigned int in hexadecimal255ff
%fa double, 6 decimals by default2.52.500000
%ea double in scientific notation1234.51.234500e+03
%ga double, shortest of %f/%e2.52.5
%ca single character'x'x
%sa 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: printf cannot check that the values match the specifiers at run time. If you pass a double to %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
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:

escapemeaning
\nnewline
\ttab
\\a backslash
\"a double quote inside a string
\'a single quote (needed in character literals: '\'')
\0the 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:

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:

  1. 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.
  2. 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.
  3. Rebuild after each fix. Many of the remaining messages may disappear.
  4. 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

Recap

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);.

Exercises

Exercise 1 · quiz

Predict the output

What does this print? (The | characters are printed too; they show where the field ends.)

printf("[%5.2f|%-4d|%03d]\n", 3.14159, 7, 7);
Answer
  • [ 3.14|7 |007]

%5.2f: at least 5 characters wide, 2 digits after the point, right-aligned, so " 3.14". %-4d: at least 4 wide, the minus means left-aligned, so "7 ". %03d: at least 3 wide, padded with zeros, so "007". Widths are minimums: a longer value is never cut off.

Exercise 2 · write code

Print a receipt

Use printf with field widths to print exactly this receipt (item names left-aligned in a 10-character field, quantities right-aligned in 3 characters, prices right-aligned in 7 characters with 2 decimals):

Item       Qty   Price
coffee       2    3.50
bagel        1    2.25
total             9.25

The header line is literally Item Qty Price. Print the numbers with format specifiers, not by typing the digits into the format string.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    printf("Item       Qty   Price\n");
    /* print the three remaining lines with %-10s, %3d and %7.2f */
    return 0;
}

Example

expected output

Item       Qty   Price
coffee       2    3.50
bagel        1    2.25
total             9.25
Hint 1

%-10s prints a string left-aligned in 10 columns; %3d an int right-aligned in 3; %7.2f a decimal number in 7 columns with 2 digits after the point.

Hint 2

Count the spaces: 10 for the name, a space, 3 for the quantity, a space, 7 for the price.

Hint 3

For the total line, replace %3d with three spaces: printf("%-10s %7.2f\n", "total", 9.25);

Reference solution
#include <stdio.h>

int main(void)
{
    printf("Item       Qty   Price\n");
    printf("%-10s %3d %7.2f\n", "coffee", 2, 3.50);
    printf("%-10s %3d %7.2f\n", "bagel", 1, 2.25);
    printf("%-10s     %7.2f\n", "total", 2 * 3.50 + 2.25);
    return 0;
}

Exercise 3 · write code

Make it compile

The starter has three mistakes that stop it from compiling. Read the compiler messages (press Run), fix them one at a time from the top, and get the program to print exactly:

C has 32 keywords.
That is 100% fine.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    printf("C has %d keywords.\n", 32)
    prinft("That is 100%% fine.\n");
    return 0;

Example

expected output

C has 32 keywords.
That is 100% fine.
Hint 1

The first error is reported on the line after the missing semicolon.

Hint 2

One function name is misspelled; the compiler even suggests the right one.

Hint 3

"expected declaration or statement at end of input" means a brace was never closed.

Reference solution
#include <stdio.h>

int main(void)
{
    printf("C has %d keywords.\n", 32);
    printf("That is 100%% fine.\n");
    return 0;
}

Exercise 4 · write code

Clean up the warnings

This program compiles and prints the right text, but gcc warns about two things. Warnings are the compiler telling you that legal code looks like a mistake. Fix the program so that it still prints exactly

Done

and compiles with no warnings at all. The check rebuilds your file with -Wall -Wextra -Werror, which turns every warning into an error.

File: main.c

Starting point

#include <stdio.h>

int main(void)
{
    int count = 3;
    int total = 0;
    printf("Done\n", count);
    return 0;
}

Example

expected output

Done
Hint 1

Read each warning: one is about a variable that is never used, the other about printf receiving an argument its format never mentions.

Hint 2

printf only uses arguments that have a matching % specifier. Remove what is not needed.

Reference solution
#include <stdio.h>

int main(void)
{
    printf("Done\n");
    return 0;
}

Exercise 5 · quiz

Comments

What does this program print?

#include <stdio.h>

int main(void)
{
    // printf("one\n");
    printf("two\n"); /* printf("three\n"); */
    /*
    printf("four\n");
    */
    printf("five\n");
    return 0;
}
Answer
  • two, then five

// comments out the rest of its line. /* ... */ comments out everything up to the next */, across lines if needed. Comments are removed by the preprocessor before compilation, so they cost nothing at run time. Note that /* */ comments do not nest: /* a /* b */ c */ ends at the first */, leaving c */ as code and a compile error.