Compiler flags and optimisation
The compiler has more to say than errors. The warning flags decide how much of it you hear, the optimisation flags decide how much of the source survives into the binary, and the debug flag decides whether a backtrace has line numbers. A build is the combination of those choices, not a single command.
Warning flags
-Wall is a set of useful warnings, not every warning, and -Wextra adds another group such as unused parameters. Both are free at run time, so they belong in every build. The program below has an unused variable and an unused parameter, and both are reported.
#include <stdio.h>
int add(int a, int b, int scale)
{
int unused = 0;
return a + b;
}
int main(void)
{
printf("%d\n", add(2, 3, 10));
return 0;
}
gcc -Wall -Wextra warn.c -o warn
warn.c: In function 'add':
warn.c:5:9: warning: unused variable 'unused' [-Wunused-variable]
5 | int unused = 0;
| ^~~~~~
warn.c:3:27: warning: unused parameter 'scale' [-Wunused-parameter]
3 | int add(int a, int b, int scale)
| ~~~~^~~~~
The exit status is still 0 and the binary is produced; a warning is a message, not a refusal. The first line names the function, and the last bracket names the flag that can turn the warning off, which is a hint rather than a recommendation.
The standard, strictly
-Wpedantic reports everything outside the selected C standard. GCC extensions that are silently accepted in the default mode become warnings, and the program still builds. The example uses a statement expression, a GNU extension that many headers rely on.
#include <stdio.h>
int main(void)
{
int value = ({ int inner = 4; inner * 2; });
printf("%d\n", value);
return 0;
}
gcc -std=c17 -Wall -Wextra -Wpedantic pedantic.c -o pedantic
pedantic.c: In function 'main':
pedantic.c:5:17: warning: ISO C forbids braced-groups within expressions [-Wpedantic]
5 | int value = ({ int inner = 4; inner * 2; });
| ^
The course uses the default C23 mode without -Wpedantic in most lessons, because some POSIX headers use extensions. A library that must compile with every compiler adds the flag and keeps the extension out of its own code.
Optimisation levels
-O0 keeps the code close to the source, and -O2 removes and rewrites it. The assembly listing is the visible difference: the optimised loop below loses most of its instructions because the compiler computes the sum in a closed form. The line counts are from this machine and this version.
#include <stdio.h>
int main(void)
{
long total = 0;
for (int i = 0; i < 1000; i++) {
total += i;
}
printf("%ld\n", total);
return 0;
}
gcc -O0 -S loop.c -o loop0.s
wc -l loop0.s
gcc -O2 -S loop.c -o loop2.s
wc -l loop2.s
43 loop0.s
27 loop2.s
The numbers count assembly lines, not machine instructions, but the direction is clear. The optimised build is faster and harder to debug: variables disappear, lines merge, and a breakpoint may land somewhere unexpected. That is why lesson 61 debugged an -O0 binary.
Warnings as errors
-Werror turns every warning into an error, so a build that would have produced a binary stops instead. That is the flag a continuous integration job wants: the warning cannot be ignored because the build fails. The message keeps the original warning name after the equals sign.
#include <stdio.h>
int main(void)
{
int unused = 5;
printf("start\n");
return 0;
}
gcc -Wall -Wextra -Werror werror.c -o werror
werror.c: In function 'main':
werror.c:5:9: error: unused variable 'unused' [-Werror=unused-variable]
5 | int unused = 5;
| ^~~~~~
cc1: all warnings being treated as errors
The exit status is 1 and no binary is written. A project usually enables -Werror in its own test build and leaves it off for distribution, because a newer compiler may add a warning the authors have not seen yet.
Building a release without warnings
#include <stdio.h>
int add(int a, int b, int scale)
{
int unused = 0;
return a + b;
}
int main(void)
{
printf("%d\n", add(2, 3, 10));
return 0;
}
The common mistake is the opposite of -Werror: a release command without -Wall or -Wextra, where the two warnings from the first section disappear from the build log. The program still runs, and the unused parameter stays in the API until a user reports the bug it hides.
gcc -O2 warn.c -o warn_quiet; echo "status: $?"
status: 0
There is no message because no warning flag was given; the command succeeds and produces warn_quiet. The recommended release line keeps the diagnostics and the optimisation together, and adds the debug information separately:
gcc -O2 -Wall -Wextra -g warn.c -o warn_release
warn.c: In function 'add':
warn.c:5:9: warning: unused variable 'unused' [-Wunused-variable]
5 | int unused = 0;
| ^~~~~~
warn.c:3:27: warning: unused parameter 'scale' [-Wunused-parameter]
3 | int add(int a, int b, int scale)
| ~~~~^~~~~
The warnings come back and the binary keeps symbols for a production backtrace, which costs space but not speed. The only build without warnings should be the one you are consciously hiding something from.
Exercises
- Compile the first program with -Wno-unused-parameter and check which warning remains.
- Compare -O1, -O2 and -Os on the loop program and report the number of assembly lines for each.
- Add -Werror to the command of the first section and explain the exit status.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
What does
-Wextraadd to-Wall?Answer
Another group of warnings, such as unused parameters
-Wallis a useful set rather than everything, and-Wextraadds checks such as unused parameters. -
What does
-Wpedanticreport?Answer
Code outside the selected C standard
It complains about GNU extensions and other constructs that the chosen standard does not allow.
-
What changes between
-O0and-O2?Answer
The compiler rewrites and removes much of the code
The optimiser keeps values in registers and can replace a loop with a closed form, so the assembly is shorter and harder to debug.
-
What does
-Werrordo?Answer
It makes every warning fail the build
A warning becomes an error, the exit status is 1, and no binary is written.
-
This program is built with
gcc -std=c17 -Wall -Wextra warn.c -o warn. Which warning flag names the unused parameter?#include <stdio.h> int add(int a, int b, int scale){ int unused=0; return a+b; } int main(void){ printf("%d\n", add(2,3,10)); return 0; }Answer
-Wunused-parameter
The parameter
scaleis never read, and GCC reports it under-Wunused-parameter. -
What does
-Wpedanticsay about the statement expression?Answer
That ISO C forbids braced groups in expressions
The braced group is a GNU extension, so a strict standard build warns while still producing a binary.
-
How many assembly lines do the two builds produce here?
Answer
43 at -O0 and 27 at -O2
The optimised listing is shorter because the loop is reduced, though the exact counts depend on the compiler.
-
What is the status of
gcc -O2 warn.c -o warn_quietwithout warning flags?Answer
0, with no warning printed
No warning flag is given, so the unused variable and parameter produce no message and the build succeeds.
-
Why can a release build without
-Wallhide a real bug?Answer
The warnings disappear but the code still compiles
The program builds and runs, and only the missing diagnostic would have pointed at the unfinished code.
-
Why is an
-O2binary a poor debugging target?Answer
Variables and lines may be merged or removed
The optimiser rewrites the code, so a breakpoint may land elsewhere and a variable may have no home to inspect.
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