Comments and code style

A program is read far more often than it is written, and in C the source is the only documentation the compiler does not check. Style is not decoration here: a clear name or a guard clause prevents the kind of mistake that a debugger would otherwise find an hour later. The rules in this lesson are the ones the rest of the course follows.

Comments that explain why

C has two comment forms: // runs to the end of the line, and /* ... */ spans any number of lines. Both are removed by the preprocessor, so they cost nothing at run time. A comment should say why the code is written this way; the code itself already says what it does.

#include <stdio.h> /* Convert Celsius to Fahrenheit. * The formula is exact for this demo, no rounding is needed. */ double to_fahrenheit(double celsius) { return celsius * 9.0 / 5.0 + 32.0; // 9/5 as doubles, never 9/5 } int main(void) { // 100 C is the boiling point of water at sea level. printf("%.1f F\n", to_fahrenheit(100.0)); return 0; }

gcc -Wall -Wextra comments.c -o comments
./comments

212.0 F

The comment on the return line earns its place: it explains why the literals are doubles. Without it the next reader may simplify them to 9 / 5, which is integer division and evaluates to 1 instead of 1.8.

Names carry meaning

C has no namespaces, so a name must carry its context: total_seconds is better than t, and a unit in the name prevents the classic seconds-versus-milliseconds bug. Lower-case words joined by underscores are the usual convention; upper-case names are reserved for macros and constants.

#include <stdio.h> #define SECONDS_PER_MINUTE 60 int main(void) { int total_seconds = 150; int minutes = total_seconds / SECONDS_PER_MINUTE; int seconds = total_seconds % SECONDS_PER_MINUTE; printf("%d s is %d min %d s\n", total_seconds, minutes, seconds); return 0; }

gcc -Wall -Wextra names.c -o names
./names

150 s is 2 min 30 s

The macro name explains the 60, and the local names explain what is divided by what. A reader can check the arithmetic without comments.

Guard clauses

Deep nesting is harder to follow than a sequence of early returns. Check the invalid cases first, return or clamp, and leave the normal path unindented. The function below keeps a percentage inside its range with two guards and one return for the good case.

#include <stdio.h> int parse_percent(int value) { if (value < 0) { return 0; } if (value > 100) { return 100; } return value; } int main(void) { printf("%d %d %d\n", parse_percent(-5), parse_percent(42), parse_percent(140)); return 0; }

gcc -Wall -Wextra guard.c -o guard
./guard

0 42 100

Four spaces per level, braces on their own lines and one statement per line are the default of this course. Pick a style and keep it; a mixed file costs more attention than any single rule can save.

Named constants

A number whose meaning is not in its name is a magic number. Give it a name with #define or with static const; the first works in array sizes and case labels, the second has a type and is checked by the compiler.

#include <stdio.h> #define MAX_ITEMS 3 static const double TAX_RATE = 0.2; int main(void) { double price = 19.99; double total = price * MAX_ITEMS * (1.0 + TAX_RATE); printf("%d items: %.2f\n", MAX_ITEMS, total); return 0; }

gcc -Wall -Wextra constants.c -o constants
./constants

3 items: 71.96

MAX_ITEMS is an integer constant expression, so it could size an array or label a case. TAX_RATE keeps its type and can be inspected in a debugger like any other object.

Nested comments

C comments do not nest. A /* inside a block comment is just text, and the first */ closes the comment. GCC warns about the mistake, but still compiles the file, which is how a commented-out block can come back to life and break the build.

#include <stdio.h> int main(void) { /* outer comment /* inner comment */ printf("done\n"); return 0; }

gcc -Wall -Wextra nested.c -o nested
./nested

nested.c: In function 'main':
nested.c:6:5: warning: '/*' within comment [-Wcomment]
6 | /* inner comment */
| ^
done

To disable a block that already contains comments, use #if 0 and #endif; the preprocessor removes it as a unit and nested comment markers stay harmless. Lesson 42 covers the directive.

Exercises

  1. Take the temperature program and add a comment that explains the formula, then remove every comment that only repeats the code.
  2. Rename the variables in a program you wrote earlier so that every name contains its unit, and check that the program still builds.
  3. Replace the three guard returns with a single expression using the conditional operator and decide which version reads better.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. When are comments removed from a C source file?

    Answer

    By the preprocessor, before compilation

    Both comment forms are handled by the preprocessor, so they cost nothing at run time.

  2. What does this program print? #include <stdio.h> static double to_fahrenheit(double celsius) { return celsius * 9.0 / 5.0 + 32.0; } int main(void) { printf("%.1f F\n", to_fahrenheit(100.0)); return 0; }

    Answer

    212.0 F

    100 degrees Celsius is 212 degrees Fahrenheit, and the format keeps one decimal place.

  3. What does the expression 9 / 5 evaluate to?

    Answer

    1

    Both operands are integers, so the division truncates toward zero and gives 1.

  4. What does this program print? #include <stdio.h> static int parse_percent(int value) { if (value < 0) return 0; if (value > 100) return 100; return value; } int main(void) { printf("%d %d %d\n", parse_percent(-5), parse_percent(42), parse_percent(140)); return 0; }

    Answer

    0 42 100

    The two guard clauses clamp values below 0 and above 100, while 42 passes through.

  5. What is the advantage of static const double TAX_RATE over #define TAX_RATE?

    Answer

    It has a type and can be checked and inspected

    A static const object has a type, so the compiler checks it and a debugger can show it; a macro is only text.

  6. What is the purpose of a guard clause?

    Answer

    To handle invalid input early and leave the normal path flat

    Checking the invalid cases first and returning early keeps the normal path unindented and easier to read.

  7. What does this program print? #include <stdio.h> #define SECONDS_PER_MINUTE 60 int main(void) { int total_seconds = 150; int minutes = total_seconds / SECONDS_PER_MINUTE; int seconds = total_seconds % SECONDS_PER_MINUTE; printf("%d s is %d min %d s\n", total_seconds, minutes, seconds); return 0; }

    Answer

    150 s is 2 min 30 s

    150 divided by 60 is 2 with a remainder of 30, so the output is 2 minutes and 30 seconds.

  8. A block comment contains /* inner comment */. What happens?

    Answer

    GCC warns and still compiles the file

    C comments do not nest: the first */ closes the outer comment, GCC warns with -Wcomment, and the rest of the file is compiled.

  9. What does 100.0 * (9 / 5) + 32.0 evaluate to?

    Answer

    132.0

    The parentheses make 9 / 5 an integer division, which gives 1, so the result is 100 plus 32.

  10. What does #if 0 around a block of code do?

    Answer

    Removes the block before compilation

    The preprocessor drops the block between #if 0 and #endif, and nested comment markers inside it stay harmless.

The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev