nullprogram.com/blog/2023/04/29/
The major compilers have an enormous number of knobs. Most are
highly specialized, but others are generally useful even if uncommon. For
warnings, the venerable -Wall -Wextra
is a good start, but
circumstances improve by tweaking this warning set. This article covers
high-hitting development-time options in GCC, Clang, and MSVC that ought
to get more consideration.
There’s an irony that the more you use these options, the less useful they
become. Given a reasonable workflow, they are a harsh mistress in a fast,
tight feedback loop quickly breaking the habits that cause warnings and
errors. It’s a kind of self-improvement, where eventually most findings
will be false positives. With heuristics internalized, you will be able
spot the same issues just reading code — a handy skill during code review.
Static warnings
Traditionally, C and C++ compilers are by default conservative with
warnings. Unless configured otherwise, they only warn about the most
egregious issues where it’s highly confident. That’s too conservative. For
gcc
and clang
, the first order of business is turning on more warnings
with -Wall
. Despite the name, this doesn’t actually enable all
warnings. (clang
has -Weverything
which does literally this, but
trust me, you don’t want it.) However, that still falls short, and you’re
better served enabling extra warnings on with -Wextra
.
That should be the baseline on any new project, and closer to what these
compilers should do by default. Not using these means leaving value on the
table. If you come across such a project, there’s a good chance you can
find bugs statically just by using this baseline. Some warnings only occur
at higher optimization levels, so leave these on for your release builds,
too.
For MSVC, including clang-cl
, a similar baseline is /W4
. Though it
goes a bit far, warning about use of unary minus on unsigned types
(C4146), and sign conversions (C4245). If you’re using a CRT, also
disable the bogus and irresponsible “security” warnings. Putting it
together, the warning baseline becomes:
$ cl /W4 /wd4146 /wd4245 /D_CRT_SECURE_NO_WARNINGS ...
As for gcc
and clang
, I dislike unused parameter warnings, so I often
turn it off, at least while I’m working: -Wno-unused-parameter
.
Rarely is it a defect to not use a parameter. It’s common for a function
to fit a fixed prototype but not need all its parameters (e.g. WinMain
).
Were it up to me, this would not be part of -Wextra
.
I also dislike unused functions warnings: -Wno-unused-function
.
I can’t say this is wrong for the baseline since, in most cases, ultimately
I do want to know if there are unused functions, e.g. to be deleted. But
while I’m working it’s usually noise.
If I’m working with OpenMP, I may also disable warnings about
unknown pragmas: -Wno-unknown-pragmas
. One cool feature of
OpenMP is that the typical case gracefully degrades to single-threaded
behavior when not enabled. That is, compiling without -fopenmp
.
I’ll test both ways to ensure I get deterministic results, or just to ease
debugging, and I don’t want warnings when it’s disabled. It’s fine for the
baseline to have this warning, but sometimes it’s a poor match.
When working with single-precision floats, perhaps on games or graphics,
it’s easy to accidentally introduce promotion to double precision, which
can hurt performance. It could be neglecting an f
suffix on a constant
or using sin
instead of sinf
. Use -Wdouble-promotion
to
catch such mistakes. Honestly, this is important enough that it should go
into the baseline.
#define PI 3.141592653589793
float degs = ...;
float rads = degs * PI / 180; // warns about promotion
It can be awkward around variadic functions, particularly printf
, which
cannot receive float
arguments, and so implicitly converts. You’ll need
a explicit cast to disable the warning. I imagine this is the main reason
the warning is not part of -Wextra
.
float x = ...;
printf("%.17gn", (double)x);
Finally, an advanced option: -Wconversion -Wno-sign-conversion
.
It warns about implicit conversions that may result in data loss. Sign
conversions do not have data loss, the implicit conversions are useful,
and in my experience they’re not a source of defects, so I disable that
part using the second flag (like MSVC /wd4245
). The important warning
here is truncation of size values, warning about unsound uses of sizes and
subscripts. For example:
// NOTE: would be declared/defined via windows.h
typedef uint32_t DWORD;
BOOL WriteFile(HANDLE, const void *, DWORD, DWORD *, OVERLAPPED *);
void logmsg(char *msg, size_t len)
{
HANDLE err = GetStdHandle(STD_ERROR_HANDLE);
DWORD out;
WriteFile(err, msg, len, &out, 0); // len truncation warning
}
On 64-bit targets, it will warn about truncating the 64-bit len
for the
32-bit parameter. To dismiss the warning, you must either address it by
using a loop to call WriteFile
multiple times, or acknowledge the
truncation with an explicit cast and accept the consequences. In this case
I may know from context it’s impossible for the program to even construct
such a large message, so I’d use an assertion and truncate.
void logmsg(char *msg, size_t len)
{
HANDLE err = GetStdHandle(STD_ERROR_HANDLE);
DWORD out;
assert(len <= 0xffffffff);
Wri