GCC warnings explained: common messages and how to fix them
control reaches end of non-void function, unused variable, makes pointer from integer, format '%d' expects... Common GCC warnings, their cause and their fix.
The program compiles, a few yellow lines scroll past, the executable is there and it seems to work. It's tempting to move on. Don't: most of those lines describe a bug that hasn't shown itself yet, and GCC took the trouble to tell you where it is.
In short
A GCC warning flags code the language accepts but that is almost certainly not what you meant to write: a missing return, a = where you wanted ==, a %d for a size_t. The name in brackets at the end of the message, such as [-Wreturn-type], is the option that triggered it, and the keyword to search for. Always compile with -Wall -Wextra, and fix every warning as you would a bug. Since GCC 14, several former warnings (int-conversion, incompatible-pointer-types, implicit-function-declaration) are errors.
How to read a GCC warning
Every diagnostic follows the same format, and every part of it is useful. Take this one:
$ gcc -Wall -Wextra format.c -o formatformat.c: In function 'main':format.c:8:21: warning: format '%d' expects argument of type 'int', but argument 3 has type 'size_t' {aka 'long unsigned int'} [-Wformat=] 8 | printf("%s has %d letters\n", word, strlen(word)); | ~^ ~~~~~~~~~~~~ | | | | int size_t {aka long unsigned int} | %ldIn function 'main'tells you which function the problem is in, which helps when the file is long.format.c:8:21gives the file, the line and the column.warning:(orerror:) is the severity. A warning still lets the executable build, an error stops it.The message itself: that's the part you paste into a search engine.
[-Wformat=]names the option responsible.gcc --help=warningsand the GCC manual describe each one.Under the quoted line, the
~and^underline the pieces involved, with their types, and sometimes a suggested fix (%ldhere).
The suggestion isn't always the best one: %ld works on this machine, but the specifier the standard provides for a size_t is %zu.
The most common messages, in one table
Every message below comes from GCC 15.3 with -Wall -Wextra, unless noted otherwise. x and f stand for the name of your variable or function.
| Message | Option | Likely cause | Fix |
|---|---|---|---|
control reaches end of non-void function | -Wreturn-type (-Wall) | a path leaves the function without a return | add the missing return |
unused variable 'x' | -Wunused-variable (-Wall) | variable declared, never read | delete it or use it |
unused parameter 'x' | -Wunused-parameter (-Wextra) | parameter never read | use it, or (void)x; if that's deliberate |
... makes pointer from integer without a cast | -Wint-conversion, an error since GCC 14 | an integer (often a char) stored in a pointer | check the type, or look for a missing & |
comparison between pointer and integer | on by default | s[i] == "a": a character compared with a string | single quotes: 'a' |
comparison with string literal results in unspecified behavior | -Waddress (-Wall) | == between two strings | strcmp |
format '%d' expects argument of type 'int' | -Wformat (-Wall) | the specifier doesn't match the type | %zu, %ld, %s, %p... |
suggest parentheses around assignment used as truth value | -Wparentheses (-Wall) | = instead of == in a condition | == |
this statement may fall through | -Wimplicit-fallthrough (-Wextra) | a case without break | break, or [[fallthrough]]; if it's deliberate |
comparison of integer expressions of different signedness | -Wsign-compare (-Wextra) | an int compared with an unsigned or a size_t | the same signedness on both sides |
'x' is used uninitialized | -Wuninitialized, mostly with -O2 | variable read before any assignment | initialize it where you declare it |
passing argument 1 of 'f' from incompatible pointer type | -Wincompatible-pointer-types, an error since GCC 14 | a pointer of the wrong type passed to a function | fix the type, without a cast |
implicit declaration of function 'f' | an error since GCC 14 | function called with no declaration in sight | see the dedicated article |
The sections below take the most frequent ones, each with a minimal program and what it actually does when you ignore the message.
control reaches end of non-void function
The function promises to return a value, but at least one path reaches the closing brace without a return. Using the value that path returns is undefined behavior, and the result changes with the compiler options:
#include <stdio.h>
int larger(int a, int b){ if (a > b) return a; if (b > a) return b;}
int main(void){ printf("%d\n", larger(7, 7)); return 0;}$ gcc -Wall -Wextra max.c -o max && ./maxmax.c: In function 'larger':max.c:9:1: warning: control reaches end of non-void function [-Wreturn-type] 9 | } | ^7$ gcc -Wall -Wextra -O2 max.c -o max && ./maxmax.c: In function 'larger':max.c:9:1: warning: control reaches end of non-void function [-Wreturn-type] 9 | } | ^0Without optimization, the program prints 7: a value that happened to be left there, not a computed result. With -O2, it prints 0. The a == b case has no return, and that's the whole bug. Replace the second if with a plain return b; and the warning goes away, because every path now returns something. There's more on results that change from one build to the next in undefined behavior in C.
unused variable and unused parameter
A variable that is declared and never read, or a parameter the function ignores. The program works, but it's often the sign of a computation you forgot.
int double_of(int n, int factor){ int result;
return n * 2;}
int main(void){ return double_of(1, 2);}$ gcc -Wall -Wextra unused.c -o unusedunused.c: In function 'double_of':unused.c:3:9: warning: unused variable 'result' [-Wunused-variable] 3 | int result; | ^~~~~~unused.c:1:26: warning: unused parameter 'factor' [-Wunused-parameter] 1 | int double_of(int n, int factor) | ~~~~^~~~~~unused variable comes from -Wall, unused parameter only from -Wextra. Before you silence the message, ask yourself why the parameter exists: here, the function should probably compute n * factor. When a parameter really is useless, for instance because the signature is imposed on you, write (void)factor; in the body. That's the idiomatic way to tell the compiler you meant it.
With -Werror, every warning becomes an error and no executable is produced:
$ gcc -Wall -Wextra -Werror unused.c -o unusedunused.c: In function 'double_of':unused.c:3:9: error: unused variable 'result' [-Werror=unused-variable] 3 | int result; | ^~~~~~unused.c:1:26: error: unused parameter 'factor' [-Werror=unused-parameter] 1 | int double_of(int n, int factor) | ~~~~^~~~~~cc1: all warnings being treated as errorsfix-the-unused
scale.c doesn't compile: the grader uses -Wextra -Werror, and scale ignores its factor parameter. The trap is writing (void)factor; to make the message go away, when the function is supposed to return the product of its two parameters.
makes pointer from integer without a cast
You're storing an integer in a pointer. In practice it's almost always a type mix-up, and since GCC 14 it's an error:
#include <stdio.h>
int main(int argc, char **argv){ char *initial = argv[1][0];
if (argc > 1) printf("%c\n", *initial); return 0;}$ gcc -Wall -Wextra first.c -o firstfirst.c: In function 'main':first.c:5:21: error: initialization of 'char *' from 'char' makes pointer from integer without a cast [-Wint-conversion] 5 | char *initial = argv[1][0]; | ^~~~argv[1] is a string, a char *. argv[1][0] is its first character, a char, so a small integer. The message says exactly that: 'char *' from 'char'. The fix is char initial = argv[1][0];, then printf("%c\n", initial);. And while you're at it, the argc > 1 test has to come before anything reads argv[1]. The mirror message, makes integer from pointer without a cast, reports the opposite mistake: a pointer stored in an integer, often a "a" written where you meant 'a'.
comparison between pointer and integer
The same confusion between a character and a string, in a comparison this time:
#include <stdio.h>
int count_a(const char *s){ int n = 0;
for (int i = 0; s[i] != '\0'; i++) if (s[i] == "a") n++; return n;}
int main(void){ printf("%d\n", count_a("banana")); return 0;}$ gcc -Wall -Wextra compare.c -o comparecompare.c: In function 'count_a':compare.c:8:18: warning: comparison between pointer and integer 8 | if (s[i] == "a") | ^~compare.c:8:18: warning: comparison with string literal results in unspecified behavior [-Waddress]$ gcc -w compare.c -o compare && ./compare0The -w option turns off every warning: it's used here, and in the sections that follow, to show what the program does when you ignore them. "a" in double quotes is a string, so the address of its first character. 'a' in single quotes is the character itself. The program compares each letter of "banana" with a memory address, and finds zero as in a word that has three. This warning has no option in brackets: GCC prints it even without -Wall. The article on strings in C covers the difference in detail.
format '%d' expects argument of type 'int'
printf doesn't know the types of its arguments: it trusts the format string. If the format announces an int and you pass a size_t (what strlen returns), it reads the argument with the wrong size, and the standard calls the result undefined behavior. That's the example from the top of this page, and GCC catches it through -Wformat, which -Wall turns on.
printf("%s has %d letters\n", word, strlen(word));printf("%s has %zu letters\n", word, strlen(word));The pairs worth memorizing: %d for int, %ld for long, %zu for size_t, %u for unsigned int, %f for double, %c for a character, %s for a string, %p for a pointer (converted to void *). The printf(3) entry lists them all.
fix-the-format
print_pair prints a size_t and a long with two %ds. Compile the file as it is, read the diagnostic, then fix the format string: the tests go all the way to SIZE_MAX and LONG_MIN, where the wrong specifier doesn't get away with it.
suggest parentheses around assignment used as truth value
A = in a condition, where you meant ==:
#include <stdio.h>
int main(void){ int attempts = 0;
if (attempts = 3) printf("No attempts left\n"); return 0;}$ gcc -Wall -Wextra assign.c -o assignassign.c: In function 'main':assign.c:7:9: warning: suggest parentheses around assignment used as truth value [-Wparentheses] 7 | if (attempts = 3) | ^~~~~~~~$ gcc -w assign.c -o assign && ./assignNo attempts leftattempts = 3 stores 3 in attempts and evaluates to 3, so the condition is always true. GCC suggests parentheses because an assignment inside a condition can be deliberate, as in while ((c = getchar()) != EOF). Here, the fix is ==.
this statement may fall through
In a switch, a case without break carries on into the next one. Sometimes that's intended, very often it's an oversight. -Wextra reports it:
#include <stdio.h>
void grade(int score){ switch (score / 2) { case 10: case 9: printf("excellent\n"); case 8: printf("good\n"); break; default: printf("pass\n"); }}
int main(void){ grade(19); return 0;}$ gcc -Wall -Wextra fall.c -o fallfall.c: In function 'grade':fall.c:8:9: warning: this statement may fall through [-Wimplicit-fallthrough=] 8 | printf("excellent\n"); | ^~~~~~~~~~~~~~~~~~~~~fall.c:9:5: note: here 9 | case 8: | ^~~~$ gcc -w fall.c -o fall && ./fallexcellentgoodA score of 19 out of 20 gets two grades. The stacked case 10: and case 9: don't trigger anything: an empty case that runs into the next one is normal usage. Add break; after the first printf. If falling through is what you want, write [[fallthrough]]; there instead: it's the C23 attribute, which GCC 15 accepts by default, and the warning goes quiet.
comparison of integer expressions of different signedness
Comparing an int with an unsigned first converts the int to unsigned. A negative number then becomes a very large positive one. This warning isn't part of -Wall, which makes it a good example of what -Wall lets through:
#include <stdio.h>
int main(void){ int temperature = -5; unsigned int threshold = 10;
if (temperature < threshold) printf("below the threshold\n"); else printf("above the threshold\n"); return 0;}$ gcc -Wall threshold.c -o threshold && ./thresholdabove the threshold$ gcc -Wall -Wextra threshold.c -o thresholdthreshold.c: In function 'main':threshold.c:8:21: warning: comparison of integer expressions of different signedness: 'int' and 'unsigned int' [-Wsign-compare] 8 | if (temperature < threshold) | ^With -Wall alone, there's no message and the result is wrong: -5 would be above 10. Converted to unsigned int, -5 becomes 4294967291. The case you'll meet most is i < strlen(s) with an int i: it only goes wrong if i can be negative, but the warning shows up anyway. Declare i as a size_t, or keep both sides signed.
fix-the-warnings
clamp.c refuses to compile on three diagnostics at once: an unused parameter, an unused variable and a signed/unsigned comparison. Fix them one at a time, reading each one: the hi parameter is unused because the branch that needs it was never written.
Which options to turn on, and what -Wall leaves out
Despite its name, -Wall doesn't turn on every warning. It's a baseline, and -Wextra adds to it. The other options come on top, depending on what you need:
| Option | What it adds |
|---|---|
-Wall | the baseline: return-type, unused-variable, format, parentheses, uninitialized... |
-Wextra | unused-parameter, sign-compare, implicit-fallthrough, and others |
-Wpedantic | everything ISO C rejects, GCC extensions included |
-Werror | turns every warning into an error |
-Wconversion | implicit conversions that can change a value |
-Wshadow | a local variable that hides another one |
-Wmissing-prototypes | a global function defined with no prototype in sight |
-g | no warnings: keeps names and line numbers for gdb and Valgrind |
Here's something -Wconversion catches and -Wall -Wextra ignore:
#include <stdio.h>
int main(void){ double average = 13.75; int rounded = average;
printf("%d\n", rounded); return 0;}$ gcc -Wall -Wextra conv.c -o conv && ./conv13$ gcc -Wall -Wextra -Wconversion conv.c -o convconv.c: In function 'main':conv.c:6:19: warning: conversion from 'double' to 'int' may change value [-Wfloat-conversion] 6 | int rounded = average; | ^~~~~~~average is 13.75 and rounded gets 13: the fractional part is dropped without a word. -Wpedantic, for its part, flags GCC extensions the standard doesn't accept, such as ranges in a case:
#include <stdio.h>
int main(void){ int score = 14;
switch (score) { case 0 ... 9: printf("fail\n"); break; default: printf("ok\n"); } return 0;}$ gcc -Wall -Wextra ranges.c -o ranges$ gcc -Wall -Wextra -Wpedantic ranges.c -o rangesranges.c: In function 'main':ranges.c:8:5: warning: ISO C does not support range expressions in switch statements before C2Y [-Wpedantic] 8 | case 0 ... 9: | ^~~~One last trap: some warnings depend on optimization, because the analysis that produces them is part of the optimizer. A variable that's never initialized goes unnoticed at -O0:
#include <stdio.h>
int main(void){ int sum;
for (int i = 1; i <= 3; i++) sum += i; printf("%d\n", sum); return 0;}$ gcc -Wall -Wextra uninit.c -o uninit && ./uninit8$ gcc -Wall -Wextra -O2 uninit.c -o uninituninit.c: In function 'main':uninit.c:8:13: warning: 'sum' is used uninitialized [-Wuninitialized] 8 | sum += i; | ~~~~^~~~uninit.c:5:9: note: 'sum' was declared here 5 | int sum; | ^~~The loop adds 1, 2 and 3 to sum, which was never set to 0: the program prints 8 instead of 6. Our advice: develop with gcc -Wall -Wextra -g, add -Werror as soon as your code compiles cleanly so that no warning can sneak back in, and build with -O2 from time to time to get the extra analysis. The gcc(1) entry sums up the compiler options.
Every exercise in the "Read the error" module of the C path starts from a file that doesn't compile, from a syntax error to a list of three diagnostics:
Frequently asked questions
Does a warning stop the program from compiling?
No, unless you pass -Werror, which turns all of them into errors. But since GCC 14, several messages that used to be warnings are errors by default: implicit-function-declaration, implicit-int, int-conversion, incompatible-pointer-types, return-mismatch and declaration-missing-parameter-type.
How do I turn off one specific warning?
Use -Wno- followed by the name in brackets: -Wno-unused-parameter turns off [-Wunused-parameter]. For a parameter that's unused on purpose, (void)x; in the body is more precise, since it only applies to that one variable. Only turn a warning off once you understand why it appears.
Why doesn't my classmate get the same warning as me?
Because warnings depend on the GCC version, the options you pass and the optimization level. 'x' is used uninitialized doesn't appear at -O0 in the example above, and code that only got a warning from GCC 13 is rejected by GCC 14. Compare your two commands, and the output of gcc --version.
Should I fix warnings in a program that works?
Yes. In the examples above, every program that "works" gives a wrong result in at least one case: a value that changes with -O2, a comparison that comes out backwards, one grade too many. A warning fixed right away costs a minute; the same bug found later costs an evening of debugging.
Sources
