Warnings GCC : les messages courants expliqués et corrigés

control reaches end of non-void function, unused variable, makes pointer from integer, format '%d' expects... Les warnings GCC courants, leur cause, leur correction.

Par
Publié le

Le programme compile, quelques lignes jaunes défilent, l'exécutable est là et il a l'air de marcher. La tentation est forte de passer à la suite. Mauvaise idée : la plupart de ces lignes décrivent un bug qui ne s'est pas encore montré, et GCC a pris la peine de te dire où il est.

En bref

Un warning de GCC signale du code accepté par le langage mais presque sûrement différent de ce que tu voulais écrire : un return oublié, un = à la place de ==, un %d pour un size_t. Le nom entre crochets à la fin du message, comme [-Wreturn-type], est l'option qui l'a déclenché, et le mot-clé à chercher. Compile toujours avec -Wall -Wextra, et corrige chaque warning comme un bug. Depuis GCC 14, plusieurs anciens warnings (int-conversion, incompatible-pointer-types, implicit-function-declaration) sont devenus des erreurs.

Comment lire un warning de GCC

Chaque diagnostic suit le même format, et chaque morceau sert. Prends celui-ci :

Terminal
$ gcc -Wall -Wextra format.c -o formatformat.c: In function 'main':format.c:8:22: warning: format '%d' expects argument of type 'int', but argument 3 has type 'size_t' {aka 'long unsigned int'} [-Wformat=]    8 |     printf("%s fait %d lettres\n", mot, strlen(mot));      |                     ~^                  ~~~~~~~~~~~      |                      |                  |      |                      int                size_t {aka long unsigned int}      |                     %ld
  1. In function 'main' situe le problème dans une fonction, utile quand le fichier est long.

  2. format.c:8:22 donne le fichier, la ligne et la colonne.

  3. warning: (ou error:) indique la gravité. Un warning laisse l'exécutable se construire, une erreur l'empêche.

  4. Le message lui-même, en anglais : c'est lui qu'on tape dans un moteur de recherche.

  5. [-Wformat=] nomme l'option responsable. gcc --help=warnings ou la documentation de GCC décrivent chacune.

  6. Sous la ligne citée, les ~ et ^ soulignent les morceaux en cause, avec leurs types, et parfois une correction suggérée (%ld ici).

La suggestion n'est pas toujours la meilleure : %ld fonctionne sur cette machine, mais le spécificateur prévu par la norme pour un size_t est %zu.

Les messages les plus courants, en un tableau

Tous ces messages ont été obtenus avec GCC 15.3 et -Wall -Wextra, sauf mention contraire. x et f remplacent le nom de ta variable ou de ta fonction.

MessageOptionCause probableCorrection
control reaches end of non-void function-Wreturn-type (-Wall)un chemin sort de la fonction sans returnajouter le return manquant
unused variable 'x'-Wunused-variable (-Wall)variable déclarée, jamais luela supprimer ou s'en servir
unused parameter 'x'-Wunused-parameter (-Wextra)paramètre jamais lus'en servir, ou (void)x; si c'est voulu
... makes pointer from integer without a cast-Wint-conversion, erreur depuis GCC 14un entier (souvent un char) rangé dans un pointeurrevoir le type, ou le & oublié
comparison between pointer and integeractif par défauts[i] == "a" : un caractère comparé à une chaîneguillemets simples : 'a'
comparison with string literal results in unspecified behavior-Waddress (-Wall)== entre deux chaînesstrcmp
format '%d' expects argument of type 'int'-Wformat (-Wall)spécificateur qui ne correspond pas au type%zu, %ld, %s, %p...
suggest parentheses around assignment used as truth value-Wparentheses (-Wall)= au lieu de == dans une condition==
this statement may fall through-Wimplicit-fallthrough (-Wextra)case sans breakbreak, ou [[fallthrough]]; si c'est voulu
comparison of integer expressions of different signedness-Wsign-compare (-Wextra)int comparé à un unsigned ou un size_tmême signe des deux côtés
'x' is used uninitialized-Wuninitialized, surtout avec -O2variable lue avant toute affectationl'initialiser à la déclaration
passing argument 1 of 'f' from incompatible pointer type-Wincompatible-pointer-types, erreur depuis GCC 14pointeur du mauvais type passé à une fonctioncorriger le type, sans cast
implicit declaration of function 'f'erreur depuis GCC 14fonction appelée sans déclarationvoir l'article dédié

Les sections suivantes reprennent les plus fréquents avec un programme minimal et ce qu'il fait vraiment quand on ignore le message.

control reaches end of non-void function

La fonction promet de renvoyer une valeur, mais au moins un chemin arrive à l'accolade fermante sans return. Utiliser la valeur renvoyée par ce chemin est un comportement indéfini, et le résultat change avec les options :

max.cÀ éviter
#include <stdio.h>
int plus_grand(int a, int b){    if (a > b)        return a;    if (b > a)        return b;}
int main(void){    printf("%d\n", plus_grand(7, 7));    return 0;}
Terminal
$ gcc -Wall -Wextra max.c -o max && ./maxmax.c: In function 'plus_grand':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 'plus_grand':max.c:9:1: warning: control reaches end of non-void function [-Wreturn-type]    9 | }      | ^0

Sans optimisation, le programme affiche 7 : une valeur restée là par hasard, pas un résultat calculé. Avec -O2, il affiche 0. Le cas a == b n'a pas de return, et c'est tout le bug. Remplace le second if par un simple return b; et le warning disparaît, parce que tous les chemins renvoient quelque chose. Plus de détails sur ce genre de résultat qui change d'une compilation à l'autre dans comportement indéfini en C.

unused variable et unused parameter

Une variable déclarée et jamais lue, ou un paramètre que la fonction ignore. Le programme marche, mais c'est souvent le signe d'un calcul oublié.

unused.cÀ éviter
int double_de(int n, int facteur){    int resultat;
    return n * 2;}
int main(void){    return double_de(1, 2);}
Terminal
$ gcc -Wall -Wextra unused.c -o unusedunused.c: In function 'double_de':unused.c:3:9: warning: unused variable 'resultat' [-Wunused-variable]    3 |     int resultat;      |         ^~~~~~~~unused.c:1:26: warning: unused parameter 'facteur' [-Wunused-parameter]    1 | int double_de(int n, int facteur)      |                      ~~~~^~~~~~~

unused variable vient de -Wall, unused parameter seulement de -Wextra. Avant de faire taire le message, demande-toi pourquoi le paramètre existe : ici, la fonction devrait sans doute calculer n * facteur. Quand un paramètre est vraiment inutile, par exemple parce que la signature est imposée, écris (void)facteur; dans le corps : c'est la manière idiomatique de dire au compilateur que c'est voulu.

Avec -Werror, chaque warning devient une erreur et l'exécutable n'est pas produit :

Terminal
$ gcc -Wall -Wextra -Werror unused.c -o unusedunused.c: In function 'double_de':unused.c:3:9: error: unused variable 'resultat' [-Werror=unused-variable]    3 |     int resultat;      |         ^~~~~~~~unused.c:1:26: error: unused parameter 'facteur' [-Werror=unused-parameter]    1 | int double_de(int n, int facteur)      |                      ~~~~^~~~~~~cc1: all warnings being treated as errors
C

fix-the-unused

scale.c ne compile pas : le correcteur utilise -Wextra -Werror, et scale ignore son paramètre factor. Le piège est d'écrire (void)factor; pour faire taire le message, alors que la fonction doit renvoyer le produit des deux paramètres.

Ouvrir l'exercice

makes pointer from integer without a cast

Tu ranges un entier dans un pointeur. En pratique, c'est presque toujours une confusion de type, et depuis GCC 14 c'est une erreur :

premier.cÀ éviter
#include <stdio.h>
int main(int argc, char **argv){    char *initiale = argv[1][0];
    if (argc > 1)        printf("%c\n", *initiale);    return 0;}
Terminal
$ gcc -Wall -Wextra premier.c -o premierpremier.c: In function 'main':premier.c:5:22: error: initialization of 'char *' from 'char' makes pointer from integer without a cast [-Wint-conversion]    5 |     char *initiale = argv[1][0];      |                      ^~~~

argv[1] est une chaîne, un char *. argv[1][0] est son premier caractère, un char, donc un petit entier. Le message dit exactement ça : 'char *' from 'char'. La correction est char initiale = argv[1][0];, puis printf("%c\n", initiale);. Et tant qu'on y est, le test argc > 1 doit venir avant la lecture de argv[1]. Le message symétrique, makes integer from pointer without a cast, signale l'erreur inverse : un pointeur rangé dans un entier, souvent un "a" écrit à la place de 'a'.

comparison between pointer and integer

Même confusion entre caractère et chaîne, dans une comparaison cette fois :

compare.cÀ éviter
#include <stdio.h>
int compte_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", compte_a("banane"));    return 0;}
Terminal
$ gcc -Wall -Wextra compare.c -o comparecompare.c: In function 'compte_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

L'option -w coupe tous les warnings : on s'en sert ici, et dans les sections suivantes, pour montrer ce que fait le programme quand on les ignore. "a" entre guillemets doubles est une chaîne, donc l'adresse de son premier caractère. 'a' entre guillemets simples est le caractère lui-même. Le programme compare chaque lettre de « banane » à une adresse mémoire, et trouve zéro a. Ce warning n'a pas d'option entre crochets : GCC l'affiche même sans -Wall. L'article sur les chaînes de caractères en C détaille la différence.

format '%d' expects argument of type 'int'

printf ne connaît pas le type de ses arguments : il se fie au format. Si le format annonce un int et que tu passes un size_t (ce que renvoie strlen), il lit l'argument avec la mauvaise taille, et la norme qualifie le résultat de comportement indéfini. C'est l'exemple du début de l'article, et GCC le repère grâce à -Wformat, inclus dans -Wall.

Format fauxÀ éviter
printf("%s fait %d lettres\n", mot, strlen(mot));
Format justeCorrect
printf("%s fait %zu lettres\n", mot, strlen(mot));

Les correspondances à retenir : %d pour int, %ld pour long, %zu pour size_t, %u pour unsigned int, %f pour double, %c pour un caractère, %s pour une chaîne, %p pour un pointeur (converti en void *). La fiche printf(3) les reprend toutes.

C

fix-the-format

print_pair affiche un size_t et un long avec deux %d. Compile le fichier tel quel, lis le diagnostic, puis corrige la chaîne de format : les tests vont jusqu'à SIZE_MAX et LONG_MIN, là où un mauvais spécificateur ne pardonne pas.

Ouvrir l'exercice

suggest parentheses around assignment used as truth value

Un = dans une condition, là où tu voulais == :

assign.cÀ éviter
#include <stdio.h>
int main(void){    int essais = 0;
    if (essais = 3)        printf("Plus d'essais\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 (essais = 3)      |         ^~~~~~$ gcc -w assign.c -o assign && ./assignPlus d'essais

essais = 3 range 3 dans essais et vaut 3, donc la condition est toujours vraie. GCC suggère des parenthèses parce qu'une affectation dans une condition peut être voulue, comme dans while ((c = getchar()) != EOF). Ici, la correction est ==.

this statement may fall through

Dans un switch, un case sans break continue dans le suivant. C'est parfois voulu, très souvent un oubli. -Wextra le signale :

fall.cÀ éviter
#include <stdio.h>
void mention(int note){    switch (note / 2) {    case 10:    case 9:        printf("très bien\n");    case 8:        printf("bien\n");        break;    default:        printf("passable\n");    }}
int main(void){    mention(19);    return 0;}
Terminal
$ gcc -Wall -Wextra fall.c -o fallfall.c: In function 'mention':fall.c:8:9: warning: this statement may fall through [-Wimplicit-fallthrough=]    8 |         printf("très bien\n");      |         ^~~~~~~~~~~~~~~~~~~~~fall.c:9:5: note: here    9 |     case 8:      |     ^~~~$ gcc -w fall.c -o fall && ./falltrès bienbien

Un 19 obtient deux mentions. Les case 10: et case 9: collés ne déclenchent rien : un case vide qui enchaîne sur le suivant est un usage normal. Ajoute break; après le premier printf. Si la continuation est voulue, écris [[fallthrough]]; à la place : c'est l'attribut de C23, que GCC 15 accepte par défaut, et le warning se tait.

comparison of integer expressions of different signedness

Comparer un int à un unsigned convertit d'abord le int en unsigned. Un nombre négatif devient alors un très grand nombre positif. Ce warning n'est pas dans -Wall, et c'est un bon exemple de ce que -Wall laisse passer :

seuil.cÀ éviter
#include <stdio.h>
int main(void){    int temperature = -5;    unsigned int seuil = 10;
    if (temperature < seuil)        printf("en dessous du seuil\n");    else        printf("au-dessus du seuil\n");    return 0;}
Terminal
$ gcc -Wall seuil.c -o seuil && ./seuilau-dessus du seuil$ gcc -Wall -Wextra seuil.c -o seuilseuil.c: In function 'main':seuil.c:8:21: warning: comparison of integer expressions of different signedness: 'int' and 'unsigned int' [-Wsign-compare]    8 |     if (temperature < seuil)      |                     ^

Avec -Wall seul, aucun message et un résultat faux : -5 serait au-dessus de 10. Converti en unsigned int, -5 vaut 4294967291. Le cas le plus fréquent est i < strlen(s) avec un int i : il ne pose problème que si i peut être négatif, mais le warning apparaît quand même. Déclare i en size_t, ou garde les deux côtés signés.

C

fix-the-warnings

clamp.c refuse de compiler sur trois diagnostics à la fois : un paramètre inutilisé, une variable inutilisée et une comparaison entre signé et non signé. Corrige-les un par un, en lisant chacun : le paramètre hi est inutilisé parce que la branche qui en a besoin n'a jamais été écrite.

Ouvrir l'exercice

Quelles options activer, et ce que -Wall n'active pas

Malgré son nom, -Wall n'active pas tous les warnings. C'est un socle, que -Wextra complète. Les autres options s'ajoutent selon le besoin :

OptionCe qu'elle ajoute
-Wallle socle : return-type, unused-variable, format, parentheses, uninitialized...
-Wextraunused-parameter, sign-compare, implicit-fallthrough, et d'autres
-Wpedantictout ce que la norme ISO refuse, y compris les extensions de GCC
-Werrortransforme chaque warning en erreur
-Wconversionles conversions implicites qui peuvent changer une valeur
-Wshadowune variable locale qui en masque une autre
-Wmissing-prototypesune fonction globale définie sans prototype visible
-gaucun warning : garde les noms et les lignes pour gdb et Valgrind

Un exemple de ce que -Wconversion attrape et que -Wall -Wextra ignorent :

conv.c
#include <stdio.h>
int main(void){    double moyenne = 13.75;    int arrondi = moyenne;
    printf("%d\n", arrondi);    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 arrondi = moyenne;      |                   ^~~~~~~

moyenne vaut 13.75 et arrondi reçoit 13 : la partie décimale est tronquée sans un mot. -Wpedantic, lui, repère les extensions de GCC, comme les intervalles dans un case, que la norme n'accepte pas :

ranges.c
#include <stdio.h>
int main(void){    int note = 14;
    switch (note) {    case 0 ... 9:        printf("insuffisant\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:      |     ^~~~

Dernier piège : certains warnings dépendent de l'optimisation, parce que l'analyse qui les produit en fait partie. Une variable jamais initialisée passe inaperçue en -O0 :

uninit.cÀ éviter
#include <stdio.h>
int main(void){    int somme;
    for (int i = 1; i <= 3; i++)        somme += i;    printf("%d\n", somme);    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:15: warning: 'somme' is used uninitialized [-Wuninitialized]    8 |         somme += i;      |         ~~~~~~^~~~uninit.c:5:9: note: 'somme' was declared here    5 |     int somme;      |         ^~~~~

La boucle ajoute 1, 2 et 3 à somme, qui n'a jamais été mise à 0 : le programme affiche 8 au lieu de 6. Notre avis : développe avec gcc -Wall -Wextra -g, ajoute -Werror dès que ton code compile proprement pour ne plus laisser revenir un warning, et compile de temps en temps en -O2 pour profiter des analyses supplémentaires. La fiche gcc(1) résume les options de compilation.

Les exercices du module « Read the error » du parcours C partent tous d'un fichier qui ne compile pas, de l'erreur de syntaxe à la liste de trois diagnostics :

Read the error
5 exercices

Questions fréquentes

Un warning empêche-t-il le programme de compiler ?

Non, sauf avec -Werror, qui les transforme tous en erreurs. Mais depuis GCC 14, plusieurs messages qui étaient des warnings sont des erreurs par défaut : implicit-function-declaration, implicit-int, int-conversion, incompatible-pointer-types, return-mismatch et declaration-missing-parameter-type.

Comment désactiver un warning précis ?

-Wno- suivi du nom entre crochets : -Wno-unused-parameter désactive [-Wunused-parameter]. Pour un paramètre inutilisé à dessein, (void)x; dans le corps est plus précis, puisqu'il ne vaut que pour cette variable. Ne désactive un warning qu'après avoir compris pourquoi il apparaît.

Pourquoi mon camarade n'a pas le même warning que moi ?

Parce que les warnings dépendent de la version de GCC, des options passées et du niveau d'optimisation. 'x' is used uninitialized n'apparaît pas en -O0 sur l'exemple ci-dessus, et un code qui ne donnait qu'un warning avec GCC 13 est refusé par GCC 14. Compare vos deux commandes, et la sortie de gcc --version.

Faut-il corriger les warnings d'un programme qui marche ?

Oui. Sur les exemples de cet article, chaque programme qui « marche » donne un résultat faux dans au moins un cas : une valeur qui change avec -O2, une comparaison inversée, une mention en trop. Un warning corrigé tout de suite coûte une minute, le même bug retrouvé plus tard coûte une soirée de débogage.

Sources

À toi d'écrire le code.