Learn C Programming

Lesson 5 of 8 · Meet C: Your First Programs, Symbol by Symbol

Module 1 · Meet C: Your First Programs, Symbol by Symbol

Comments, Whitespace and Code a Human Can Read

FreeReading

In this lesson

  • Write both comment styles, and say where each one fits.
  • Explain what a comment is for, and what it should never be used for.
  • Lay out a program with one indentation style and names a stranger can read.

Amara and Kenji hand in the same tile-ordering program. Both compile with no warnings. Both print the right answer.

Amara's takes ten seconds to read. Kenji's takes ten minutes, and he wrote it yesterday. Nothing in this lesson changes what a program does; all of it changes how long the next person needs to understand it.

The compiler is the one reader in your life who never complains about your style. Every other reader does, and one of them is you in six months.

Two comment styles, and where each one fits

A comment is a note for humans. The preprocessor removes every comment before the compiler sees your code, so a comment can never change what a program does.

C gives you two forms.

// runs to the end of the line. It came in with C99, and it is what you want for a short note beside a line or above a small group of lines.

/* ... */ can span as many lines as you like, and it ends only when you close it. Use it for a paragraph at the top of a file or a function.

Two rules come with the second form. It does not nest, so the first */ closes it. And if you forget the */ entirely, GCC says error: unterminated comment and everything after your /* has silently become a comment.

So // is for a sentence and /* */ is for a paragraph, and an unclosed /* can swallow a whole function.

A comment says why, not what

This is the one rule that separates a useful comment from noise. The code already says what it does. A comment earns its place by saying something the code cannot.

Noise, and everybody writes it at first:

total = total + marks;   /* add marks to total */

Useful, because it explains a decision:

total = total + marks;   /* the retake mark replaces nothing, it stacks */

Four kinds of comment are almost always worth writing. Why this approach and not the obvious one. Where a number came from. What a line assumes about its input. And a warning to the next person about a trap.

One kind is always worth deleting: a comment that repeats the line beside it. It doubles the reading and it goes stale the moment the line changes.

So if a comment would survive being read out with the code hidden, it is probably saying something useful.

Names: the cheapest documentation there is

A name is a comment that the compiler checks for you, because a wrong name is still spelled the same way everywhere.

C allows letters, digits and the underscore in a name, and a name may not start with a digit. That leaves you a choice of style, and this track uses snake_case: small letters with underscores between words, as in total_marks and boxes_needed.

Three tests for a name:

  • Would a stranger guess what it holds? total_score yes, ts no.
  • Does it say the unit or the kind? delay_seconds beats delay, and is_valid beats flag.
  • Is it honest? A variable called area that holds a perimeter is worse than one called x, because it tells the reader something false.

Short names are fine for short lives. A loop counter called i is a tradition, not a failure, because it exists for three lines. A variable that lives for fifty lines earns fifty characters of thought.

So a good name removes the need for a comment, and a bad name cannot be rescued by one.

Indentation and braces: the one style this track uses

The compiler ignores your layout completely. Every line below is the same program to it, and only one of them is a program you want to inherit.

This track uses one style, stated once, so that every example you read is laid out the same way:

  • Four spaces per level of indentation, never a tab, and never a mix of the two.
  • The opening brace of a function on its own line, and its closing brace in the first column.
  • One statement per line. Two statements on one line save nothing and hide one of them.
  • A blank line between groups of related lines, the way paragraphs work in a book.
  • A space around every binary operator: a * b, not a*b.

Other projects choose differently, and they are not wrong. The Linux kernel uses tabs eight wide and puts the brace on the same line. What matters is that a file, and ideally a project, picks one and never argues again.

So the style you use is a decision you make once, and the one thing that is always wrong is two styles in one file.

Whitespace the compiler ignores, and the reader does not

Whitespace means spaces, tabs and blank lines. Between tokens, C treats any amount of it as one separator, so int a = 5; and int a=5; are identical to the compiler.

Two places where it is not free. You cannot split a keyword or a name, so in t a; is an error. And inside a string literal, every space is a real character that gets printed.

The useful consequence: you can spread one long statement over several lines to line up its parts. The compiler will not mind at all.

So layout is for the reader, with exactly two places where the compiler is watching.

One honest note about this track

Every program in this track was compiled and run before it was published, and the output blocks are real output, not predictions. The tool that does it is verify-code.mjs, and it compiles every code block in both languages.

If a program here misbehaves for you, look first for a typing mistake, then tell us. That is the same promise the printed book makes. It is why you can trust an output block enough to compare yours against it.

The two comment forms

// A single-line comment. Runs to the end of this line.

/* A block comment.
   It can span as many lines as you like,
   and it ends only when you close it. */

int marks = 90;   // a comment may sit after code too
  • // needs no closing mark; the end of the line closes it.
  • /* must be closed by */, and block comments do not nest.
  • Both are removed by the preprocessor, so neither costs anything at run time.
  • Inside a string, // and /* are ordinary characters, which is how "https://progsity.io" survives.
Example 1: a working program nobody wants to inherit

This is Kenji's version. It compiles with no warnings and prints the right number of tile boxes for a 5 by 3 room.

#include <stdio.h>
int main(void){int l=5;int w=3;int a=l*w;printf("Order %d boxes\n",a);return 0;}
Order 15 boxes

Ask yourself three questions about it. What is a? Is l a length or a list? And where would you add a second room?

Run in Compiler
Example 2: the same program, laid out

Nothing is added here except spaces, line breaks and better names. The output is identical, byte for byte.

#include <stdio.h>

int main(void)
{
    int room_length = 5;
    int room_width = 3;

    int boxes_needed = room_length * room_width;

    printf("Order %d boxes\n", boxes_needed);
    return 0;
}
Order 15 boxes

The three questions from Example 1 now answer themselves, and there is an obvious blank line where a second room would go. This is what "readable" means in practice: the next change is easy to see.

Run in Compiler
Example 3: the version a professional ships

Now the comments. Notice that not one of them says what the line does; each says something the code cannot.

#include <stdio.h>

int main(void)
{
    /* Room measurements in metres, from the site survey of 12 March.
       Both are whole numbers because the supplier only cuts to the metre. */
    int room_length = 5;
    int room_width = 3;

    /* Tiles ship in boxes of one square metre, so the area is the box count. */
    int boxes_needed = room_length * room_width;

    printf("Order %d boxes\n", boxes_needed);   // the shop wants boxes, not area
    return 0;
}
Order 15 boxes

Three comments, three facts the code cannot carry: where the numbers came from, why whole numbers are enough, and why the printed word is "boxes". Delete any of them and a reader has to ask somebody.

Run in Compiler

Where this is used

  • The Linux kernel coding style. One document, Documentation/process/coding-style.rst, fixes tabs eight wide, one statement per line and short functions for 30 million lines of C. Its comment section says plainly that comments should tell you what the code does, not how, because the how is already there.
  • Git. Documentation/CodingGuidelines in the Git source does the same job for a project thousands of people send patches to. A patch that ignores it is sent back before anyone reads the logic.
  • A formatting job in CI. Many projects run clang-format on every change and fail the build on a difference. The argument about style happens once, in a config file, and never again in a review.
  • SQLite. Its source is unusually heavily commented, and that is a large part of why people read it to learn C. Comments there explain file formats and decisions, not syntax.

Common mistakes

1. A comment that repeats the code.

i = i + 1;   /* add one to i */

No message, because nothing is wrong. It is still a mistake: it doubles the reading and adds nothing. /* skip the header row */ would have been worth the space.

2. Forgetting to close a block comment.

/* fix this later
    printf("Order %d boxes\n", boxes_needed);
    return 0;
}

GCC says error: unterminated comment and then error: expected declaration or statement at end of input. Everything after the /* became a comment, including the closing brace, so the second message is about a file that now has no end.

3. A comment that has gone stale.

/* Boxes of half a square metre each. */
int boxes_needed = room_length * room_width;

No message, ever. The line was changed and the comment was not, so the file now contains a confident lie. A stale comment is worse than no comment, and it is the brain teaser below.

4. Mixing tabs and spaces.

int main(void)
{
    int a = 1;
	int b = 2;
        int c = 3;
}

No message. Those three lines are indented with four spaces, one tab and eight spaces, and they line up in one editor and not in another. Pick spaces, set your editor to insert them for the Tab key, and the problem disappears.

Brain teaser

This program compiles, runs, and prints a number. One of its comments is a lie. Find the line it lies about and say what the correct comment would be. Then decide which of the two you would fix, the comment or the code.

#include <stdio.h>

int main(void)
{
    /* Marks out of 50, doubled to give a percentage. */
    int physics = 37;
    int chemistry = 41;

    /* Average of the two subjects. */
    int total = physics + chemistry;

    printf("%d\n", total * 2);
    return 0;
}

Read each comment as a promise, then check the line under it against the promise. One of the two promises is kept in a strange way, and the other is simply false. Work out what the program prints before you decide which fix is right.

Exercise 1Easy

Reformat this program by hand, in the style this track uses. Change nothing about what it does.

#include <stdio.h>
int main(void){int p=37;int c=41;int t=p+c;printf("Total %d\n",t);printf("Subjects 2\n");return 0;}

The five checks. Four spaces per level. The braces of main on their own lines. One statement per line. Names a stranger can read. A blank line between the declarations and the printing.

Check yourself. Run your version and compare the output with the original's, line for line. Reformatting that changes the output is not reformatting; it is a bug you just wrote.

Run in Compiler
Exercise 2Medium

This program has three lines that would surprise a reader. Add exactly three comments, one for each, and every one of them must say why.

#include <stdio.h>

int main(void)
{
    int reading = 512;
    int calibrated = reading - 12;
    int percent = calibrated / 5;

    printf("Tank at %d percent\n", percent);
    return 0;
}

The three surprises. Why 12 is subtracted. Why the division is by 5. And why a tank reading is being printed as a whole number when the division loses a fraction.

Rules. Invent the reasons; you are the engineer who fitted the sensor. No comment may name an operator or repeat the arithmetic. Twelve words or fewer each.

Check yourself. Cover the code and read your three comments alone. If they tell a small story about a sensor, you have written the right kind of comment.

Run in Compiler

Common doubts

  • How many comments should a program have?

    There is no number. A good test: after a week away, could you change this file safely? If a line would make you hesitate, that line deserves a comment.

  • Do long names make a program slower?

    No. Names exist only in your source; the compiler turns them into addresses. boxes_needed and a produce the same machine code.

  • Tabs or spaces?

    Either, consistently. This track uses four spaces, the Linux kernel uses tabs of eight, and both are fine. Mixing them in one file is the only real mistake.

  • Should I comment out old code instead of deleting it?

    No. Git remembers every version, so commented-out code is dead weight a reader has to step over. Delete it and trust the history.

  • Do comments end up in the program the machine runs?

    No. The preprocessor strips them at stage 1, before the compiler ever reads your file, so they cost nothing at run time.

Key takeaways

  • // ends at the line; /* */ ends where you close it, and it does not nest.
  • A comment earns its place by saying why, because the code already says what.
  • A good name removes the need for a comment; a stale comment is worse than none.
  • This track uses four spaces, braces on their own lines, one statement per line.
  • The compiler ignores whitespace except inside a name and inside a string.
  • Every program in this track was compiled and run, and the output blocks are real.

Next you will compile and run a program yourself, in the Playground and, if you want, on your own machine with gcc.

End of lesson 5

Mark it done, and your progress moves with you.

Next: Compile and Run It Yourself: the Playground and a Local Toolchain