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.

By
Published on

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:

Terminal
$ 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}      |                    %ld
  1. In function 'main' tells you which function the problem is in, which helps when the file is long.

  2. format.c:8:21 gives the file, the line and the column.

  3. warning: (or error:) is the severity. A warning still lets the executable build, an error stops it.

  4. The message itself: that's the part you paste into a search engine.

  5. [-Wformat=] names the option responsible. gcc --help=warnings and the GCC manual describe each one.

  6. Under the quoted line, the ~ and ^ underline the pieces involved, with their types, and sometimes a suggested fix (%ld here).

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.

MessageOptionLikely causeFix
control reaches end of non-void function-Wreturn-type (-Wall)a path leaves the function without a returnadd the missing return
unused variable 'x'-Wunused-variable (-Wall)variable declared, never readdelete it or use it
unused parameter 'x'-Wunused-parameter (-Wextra)parameter never readuse it, or (void)x; if that's deliberate
... makes pointer from integer without a cast-Wint-conversion, an error since GCC 14an integer (often a char) stored in a pointercheck the type, or look for a missing &
comparison between pointer and integeron by defaults[i] == "a": a character compared with a stringsingle quotes: 'a'
comparison with string literal results in unspecified behavior-Waddress (-Wall)== between two stringsstrcmp
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 breakbreak, 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_tthe same signedness on both sides
'x' is used uninitialized-Wuninitialized, mostly with -O2variable read before any assignmentinitialize it where you declare it
passing argument 1 of 'f' from incompatible pointer type-Wincompatible-pointer-types, an error since GCC 14a pointer of the wrong type passed to a functionfix the type, without a cast
implicit declaration of function 'f'an error since GCC 14function called with no declaration in sightsee 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:

max.cAvoid
#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;}
Terminal
$ 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 | }      | ^0

Without 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.

unused.cAvoid
int double_of(int n, int factor){    int result;
    return n * 2;}
int main(void){    return double_of(1, 2);}
Terminal
$ 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:

Terminal
$ 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 errors
C

fix-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.

Open the exercise

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:

first.cAvoid
#include <stdio.h>
int main(int argc, char **argv){    char *initial = argv[1][0];
    if (argc > 1)        printf("%c\n", *initial);    return 0;}
Terminal
$ 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:

compare.cAvoid
#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;}
Terminal
$ 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 && ./compare0

The -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.

Wrong formatAvoid
printf("%s has %d letters\n", word, strlen(word));
Right formatCorrect
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.

C

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.

Open the exercise

suggest parentheses around assignment used as truth value

A = in a condition, where you meant ==:

assign.cAvoid
#include <stdio.h>
int main(void){    int attempts = 0;
    if (attempts = 3)        printf("No attempts left\n");    return 0;}
Terminal
$ 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 left

attempts = 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:

fall.cAvoid
#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;}
Terminal
$ 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 && ./fallexcellentgood

A 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:

threshold.cAvoid
#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;}
Terminal
$ 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.

C

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.

Open the exercise

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:

OptionWhat it adds
-Wallthe baseline: return-type, unused-variable, format, parentheses, uninitialized...
-Wextraunused-parameter, sign-compare, implicit-fallthrough, and others
-Wpedanticeverything ISO C rejects, GCC extensions included
-Werrorturns every warning into an error
-Wconversionimplicit conversions that can change a value
-Wshadowa local variable that hides another one
-Wmissing-prototypesa global function defined with no prototype in sight
-gno warnings: keeps names and line numbers for gdb and Valgrind

Here's something -Wconversion catches and -Wall -Wextra ignore:

conv.c
#include <stdio.h>
int main(void){    double average = 13.75;    int rounded = average;
    printf("%d\n", rounded);    return 0;}
Terminal
$ 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:

ranges.c
#include <stdio.h>
int main(void){    int score = 14;
    switch (score) {    case 0 ... 9:        printf("fail\n");        break;    default:        printf("ok\n");    }    return 0;}
Terminal
$ 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:

uninit.cAvoid
#include <stdio.h>
int main(void){    int sum;
    for (int i = 1; i <= 3; i++)        sum += i;    printf("%d\n", sum);    return 0;}
Terminal
$ 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:

Read the error
5 exercises

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

Now write it yourself.