Skip to content
VLSI Mentor

C Part I · Module 13 · Writing Correct and Reliable C

Taking Compiler Warnings Seriously

A compiler warning is a diagnosis, and almost everybody treats it as a score. This chapter measures what a good flag set is actually worth — the default three catch one defect in fifteen, a carefully chosen dozen catch four — and then explains why that number is so much lower than the marketing suggests: a flag covers a class of defect, not a share of all defects, so a set is only as good as the match between its classes and your mistakes. It also demonstrates its own central instruction twice by failing it, because the first two times these figures were measured the warnings were counted instead of read, and both answers were badly wrong in the flattering direction.

Intermediate19 min readCPart IFundamentalsCorrectness

Part I · Module 13 · Chapter 13.7 · Taking Compiler Warnings Seriously

1. What a Warning Is

A warning is the compiler reporting something it is not required to diagnose and has concluded is probably wrong. That distinction matters:

  • An error is a constraint violation. The standard requires a diagnostic and the compiler will not produce a program.
  • A warning is discretionary. The code is well-formed, the compiler builds it, and it is telling you anyway.

So every warning is the compiler volunteering. It had no obligation, it did the analysis, and it decided the result was worth your attention.

Treat a warning as a defect report until you have established otherwise. Not advice, not style, not noise — a report from the one tool that has read every line of your translation unit.

Chapters 12.2 and 13.4 make that concrete: a -Wformat warning is reporting undefined behaviour, and so is -Wshift-count-overflow. Those are not suggestions about taste.

2. The Levels

FlagWhat it is
(none)a small set the compiler always reports
-Wallthe common, high-confidence warnings. Not "all" — the name is historical
-Wextraa further set, still high-value, slightly noisier
-Wpedanticreports uses of extensions where the standard requires a diagnostic
-Weverythingclang-only: literally every warning, including mutually contradictory ones
-Werrorturns warnings into errors

Two of those need immediate correction of a common belief.

-Wall is not all. It is a curated set, and Chapter 13.5 §4 measured a consequence: -Wsign-compare is in -Wextra, not -Wall. A project building with -Wall alone does not have it.

-Weverything is not a recommendation. It includes warnings that contradict each other and warnings for stylistic choices the compiler has no business having an opinion about. §5 measures its cost.

3. What the Default Set Is Worth

Chapter 13.1 built fifteen files, each containing exactly one real defect. Against them:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  real defects found, default set ..... 1 of 15

One — the shift by 40, caught by -Wshift-count-overflow. The leak, the use-after-free, the double free, the null dereference, the uninitialised read: all silent.

Adding the three flags this curriculum has named in earlier chapters:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  real defects found, recommended set . 4 of 15

  d06_enum_range     -Wassign-enum
  d09_if_typo        -Wundef
  d10_nonliteral_fmt -Wformat-nonliteral
  d14_shift_ub       -Wshift-count-overflow

One to four. A fourfold improvement, and still four.

4. Why "4 of 15" Understates It

The obvious reading of §3 is that the extra flags are nearly worthless. That reading is wrong, and the reason is worth understanding because it applies to every tool in this module.

The census contains no defect of their kind. Nine of the recommended flags found nothing because there was nothing of theirs to find. Given one defect each:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
defect                 -Wall -Wextra -Wpedantic   with its specific flag
  shadowed variable          0                     1   (-Wshadow)
  long truncated to int      0                     1   (-Wconversion)
  const cast away            0                     1   (-Wcast-qual)
  literal through char *     0                     1   (-Wwrite-strings)
  float promoted to double   0                     1   (-Wdouble-promotion)
  () instead of (void)       1                     1   (-Wstrict-prototypes)

Five of the six are invisible to the default set. Each flag covers a class the others do not.

A warning flag's value is the class of defect it covers, not its hit rate on any particular corpus. Measuring flags against a fixed set of bugs measures the set, not the flags.

So the honest summary is two sentences rather than one number: the extra flags took the census from 1 to 4, and they independently cover six further classes, five of which nothing else here sees.

What each warning level is worth, measured. The default set of -Wall, -Wextra and -Wpedantic found 1 of the 15 census defects and produced no false positives on correct code. Adding -Wundef, -Wformat-nonliteral and -Wassign-enum raised that to 4 of 15, still with no false positives. Six further flags — shadow, conversion, cast-qual, write-strings, double-promotion and strict-prototypes — found nothing on the census because it contains no defect of their kind, and each caught its own defect when given one, five of the six being invisible to the default set. Chapter 13.1 measured the analyser and sanitizers together reaching 11 of 15, which warnings do not replace.Flag setOn the 15-defect censusFalse positives onclean code-Wall -Wextra-Wpedanticthis curriculum's default1 of 150+ three targeted flags-Wundef · -Wformat-nonliteral· -Wassign-enum4 of 150+ six morethe six flags from §4still 4 of 15the census has no defect oftheir kind0but each caught its own — 5 of6 invisible by defaultanalyser + sanitizersfrom chapter 13.111 of 15a separate runwarnings do not replace them12
Figure 1 — what each level costs and buys. The third row is the one that needs the caption: it shows no improvement on the census and is not therefore worthless, because the census contains no defect those flags are looking for — the right-hand cell records what happened when each was given one. The six flags the third row adds are -Wshadow, -Wconversion, -Wcast-qual, -Wwrite-strings, -Wdouble-promotion and -Wstrict-prototypes. The bottom row is Chapter 13.1's figure, included so the comparison is visible: warnings are the cheapest layer and the smallest, and the gap between four and eleven is the argument for everything in the next three chapters.

5. What It Costs

A flag that reports correct code is a flag that gets switched off. Measured on a correct program — a small table of records with a sum over it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  -Wall -Wextra -Wpedantic : 0 warnings
  recommended set          : 0 warnings
  -Weverything             : 1 warnings

The recommended set is free on clean code. That is the test a flag has to pass before it belongs in a build, and it is why §6's list is a dozen flags rather than -Weverything.

-Weverything is a useful exploration tool — run it once on a file to see what exists — and a poor default, because its false positives train people to ignore the output, which costs more than the flags gain.

6. A Set Worth Using

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-std=c17 -Wall -Wextra -Wpedantic
         -Wundef
         -Wformat-nonliteral
         -Wassign-enum
         -Wtautological-unsigned-zero-compare
         -Wshadow
         -Wconversion
         -Wcast-qual
         -Wwrite-strings
         -Wdouble-promotion
         -Wstrict-prototypes
         -Wmissing-prototypes

Each one, and the chapter that earned it:

FlagCatchesFrom
-Wall -Wextrathe curated sets; -Wsign-compare lives in -Wextra13.5 §4
-Wpedanticuses of extensions13.3
-Wundefa misspelled macro in #if, silently 011.7 §3
-Wformat-nonliterala format string nothing can check12.2 §4
-Wassign-enuma value outside the enumeration10.8 §3
-Wtautological-unsigned-zero-comparei >= 0 on an unsigned counter13.5 §6
-Wshadowan inner declaration hiding an outer one5.6
-Wconversionsilent narrowing — long into int13.4, 13.5
-Wcast-quala cast that removes const9.8 §7, 10.9 §5
-Wwrite-stringsa string literal reached through char *8.2
-Wdouble-promotionan unintended float → double12.2 §6
-Wstrict-prototypes, -Wmissing-prototypes() instead of (void); a function with no declaration11.1 §4, 11.3

Two of those warrant a note before you adopt them wholesale.

-Wconversion is the noisiest and the one most likely to fire on correct code — mixing size_t and int is pervasive, and Chapter 13.5 §7 is why. It is worth the noise on new code and may be worth deferring on a large existing codebase until the rest are clean.

-Wmissing-prototypes is about structure, not correctness. It reports a non-static function with no declaration, which is Chapter 11.4 §4's rule arriving as a diagnostic: either the function belongs in a header, or it should be static. Useful, and not a defect report in the same sense as the others — which is exactly the distinction §3's callout is about.

7. Reading a Warning

The chapter's middle verb, and the thing the callout in §3 is a cautionary tale about.

Read the flag name. Every message ends with [-Wsomething]. That tells you the class, which tells you whether it is reporting undefined behaviour (-Wformat, -Wshift-count-overflow), a likely mistake (-Wsign-compare), or a structural observation (-Wmissing-prototypes).

Read what it says, not that it exists. Compiler diagnostics are unusually specific — Chapter 13.5 §1's names both types, Chapter 13.6 §4's names the array and its bound, Chapter 11.4 §7's quotes the conflicting declaration with its line. The information is in the sentence.

Find out which line it is about. A warning inside a macro expansion points at the expansion, so the fix may be in the #define — Chapter 11.6's whole subject.

Then decide. Three possible conclusions, and only one of them is "ignore":

ConclusionWhat to do
it is right, and it is a defectfix the defect
it is right, and the code is intentionalmake the intent explicit so the warning goes away honestly
it is a false positivesuppress it narrowly, with a comment saying why

The middle row is the one that matters most in practice. A warning about an unused parameter is answered by (void)param;, not by deleting the warning flag. A -Wsign-compare is answered by making the types agree or by a cast you have reasoned about — Chapter 13.5 §7 — not by casting reflexively to silence it.

A cast that exists only to silence a warning is a defect with a lid on it. The cast asserts you have checked something; if you have not, you have converted a diagnostic into a silent bug.

8. -Werror, and When

-Werror makes every warning an error, so the build fails.

For your own project, and in continuous integration: yes. A warning that does not stop the build accumulates, and a build with 200 standing warnings has none, because nobody reads them. The only stable states are zero and uncountable.

With one caution: -Werror makes your build dependent on your compiler version, because a new release adds warnings. A project that builds with -Werror today may fail on next year's compiler for code that has not changed. The usual resolution is -Werror in CI and for developers, and not in the build that end users or packagers run.

And a narrower form that is often better:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-Werror=format -Werror=return-type -Werror=implicit-function-declaration

Promote to errors the specific warnings that report undefined behaviour or near-certain defects, and leave the rest as warnings. This gets most of the benefit without the version fragility.

9. Where Warnings Stop

The honest conclusion, and the reason this chapter does not end with "turn on more flags".

4 of 15 is still four. Chapter 13.1 measured the ladder: static analysis takes it to 9, sanitizers to 11, and four defects are reached by nothing automated at all.

Warnings are the cheapest layer and the smallest. They cost one line in a build file, they run on every compile, they have no false positives at the recommended level, and they find a minority of real defects.

So the right conclusion is proportion, not enthusiasm: turn them on because they are free, read them because they are specific, fix them because they are usually right — and do not mistake a clean build for a correct program, which is where this module started.

10. What You Should Be Able to Say Now

  1. An error is a required diagnostic; a warning is the compiler volunteering. Treat a warning as a defect report.
  2. -Wall is not all — it is a curated set, and -Wsign-compare is in -Wextra.
  3. Measured: the default set found 1 of 15 census defects; three targeted flags took it to 4 of 15.
  4. Both of my first two measurements were wrong, at 14 and 13, because I counted warnings instead of reading them — they were reporting my test harness.
  5. A count is not a reading, the flattering number is the one to distrust, and the cross-check against 13.1 is what caught it.
  6. "4 of 15" understates the flags: six more each caught their own class, and five of six are invisible to the default set.
  7. A flag's value is the class it covers, not its hit rate on one corpus.
  8. Measured: the recommended set produced 0 false positives on correct code; -Weverything produced 1 — which is why it is an exploration tool, not a default.
  9. The recommended set is a dozen flags, each traceable to a chapter that measured what it catches.
  10. -Wconversion is the noisiest; -Wmissing-prototypes is structural, not a defect report.
  11. Reading means: the flag name, the sentence, the real line — then fix, make the intent explicit, or suppress narrowly with a reason. A cast that only silences a warning is a defect with a lid on it.
  12. -Werror in CI, with the caveat that it ties your build to a compiler version; -Werror= on specific warnings is often better. And 4 of 15 is still four.

11. Exercises

Exercise 1 — which flag set. For each, say whether -Wall, -Wextra, or a named flag is needed, from this chapter's and Chapter 13.5's measurements.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int i = -1; unsigned u = 1; if (i < u) { }
size_t n = 0; if (n >= 0) { }
printf(fmt, x);
#if ENABLE_FEATRUE
int x = 1 << 40;
char *p = "literal"; p[0] = 'X';

Exercise 2 — read the message. For each, say what class it is in and what you would do.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: format specifies type 'int' but the argument has type 'double' [-Wformat]
warning: unused parameter 'ctx' [-Wunused-parameter]
warning: declaration shadows a local variable [-Wshadow]
warning: no previous prototype for function 'helper' [-Wmissing-prototypes]
warning: implicit conversion loses integer precision [-Wconversion]

Exercise 3 — the harness artefact. Explain, using §3's callout, why counting warning: lines gave 14 of 15 and the true figure was 4. Give the one check that would have caught it immediately.

Exercise 4 — make the intent explicit. For each, give the change that removes the warning without hiding anything.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
void handler(int sig) { }                      /* -Wunused-parameter */
for (size_t i = 0; i < n; i++) { }             /* n is int; -Wsign-compare */
int len = strlen(s);                           /* -Wconversion */

Exercise 5 — build the command. Write the compile command you would use for a new project, and justify every flag beyond -Wall -Wextra with a defect class.

Exercise 6 — the false-positive budget. Explain why -Weverything is not recommended despite catching more, using §5's measurement, and describe the one situation where you would run it.

Exercise 7 — -Werror policy. Write the policy you would adopt for a project with developers, CI, and downstream packagers. Say what each audience gets and why.

Exercise 8 — proportion. Using Figure 1, write three sentences for a colleague who believes that a build with no warnings is a build with no bugs.

12. Summary

  • An error is required; a warning is volunteered. Treat it as a defect report.
  • -Wall is not all; -Wsign-compare lives in -Wextra.
  • Measured: 1 of 15 by default, 4 of 15 with three targeted flags.
  • Two of my measurements were wrong — 14 and 13 — because I counted instead of reading; both were warnings about the test harness.
  • The flattering number is the one to distrust, and a cross-check is what caught it.
  • Six further flags each caught their own class, five invisible by default. A flag's value is coverage of a class, not a hit rate.
  • Measured: recommended set 0 false positives; -Weverything 1 — an exploration tool, not a default.
  • Read the flag name, the sentence, the real line; then fix, state the intent, or suppress narrowly. A silencing cast is a defect with a lid on it.
  • -Werror in CI, or -Werror= on specific warnings.
  • Warnings are the cheapest layer and the smallest. Four of fifteen is still four.

Every mechanism in this module so far has been something somebody else checks — the compiler, a flag, a tool that watches the program run. All of them share a limitation: they only know what the language says, not what your program means. A compiler cannot know that this index is always less than that count, that this pointer is never null by the time control reaches here, or that this function is only ever called while holding a particular invariant — because none of those facts is expressible in C's type system. They are, however, expressible as a statement that stops the program the instant it stops being true. The next chapter is about writing those statements, where they belong, and the one category of condition they must never be used for — a rule with a specific and slightly alarming reason behind it.

Standards & specifications

Governing standard
ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)

Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.