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.
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 :
$ 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} | %ldIn function 'main'situe le problème dans une fonction, utile quand le fichier est long.format.c:8:22donne le fichier, la ligne et la colonne.warning:(ouerror:) indique la gravité. Un warning laisse l'exécutable se construire, une erreur l'empêche.Le message lui-même, en anglais : c'est lui qu'on tape dans un moteur de recherche.
[-Wformat=]nomme l'option responsable.gcc --help=warningsou la documentation de GCC décrivent chacune.Sous la ligne citée, les
~et^soulignent les morceaux en cause, avec leurs types, et parfois une correction suggérée (%ldici).
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.
| Message | Option | Cause probable | Correction |
|---|---|---|---|
control reaches end of non-void function | -Wreturn-type (-Wall) | un chemin sort de la fonction sans return | ajouter le return manquant |
unused variable 'x' | -Wunused-variable (-Wall) | variable déclarée, jamais lue | la supprimer ou s'en servir |
unused parameter 'x' | -Wunused-parameter (-Wextra) | paramètre jamais lu | s'en servir, ou (void)x; si c'est voulu |
... makes pointer from integer without a cast | -Wint-conversion, erreur depuis GCC 14 | un entier (souvent un char) rangé dans un pointeur | revoir le type, ou le & oublié |
comparison between pointer and integer | actif par défaut | s[i] == "a" : un caractère comparé à une chaîne | guillemets simples : 'a' |
comparison with string literal results in unspecified behavior | -Waddress (-Wall) | == entre deux chaînes | strcmp |
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 break | break, ou [[fallthrough]]; si c'est voulu |
comparison of integer expressions of different signedness | -Wsign-compare (-Wextra) | int comparé à un unsigned ou un size_t | même signe des deux côtés |
'x' is used uninitialized | -Wuninitialized, surtout avec -O2 | variable lue avant toute affectation | l'initialiser à la déclaration |
passing argument 1 of 'f' from incompatible pointer type | -Wincompatible-pointer-types, erreur depuis GCC 14 | pointeur du mauvais type passé à une fonction | corriger le type, sans cast |
implicit declaration of function 'f' | erreur depuis GCC 14 | fonction appelée sans déclaration | voir 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 :
#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;}$ 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 | } | ^0Sans 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é.
int double_de(int n, int facteur){ int resultat;
return n * 2;}
int main(void){ return double_de(1, 2);}$ 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 :
$ 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 errorsfix-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.
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 :
#include <stdio.h>
int main(int argc, char **argv){ char *initiale = argv[1][0];
if (argc > 1) printf("%c\n", *initiale); return 0;}$ 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 :
#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;}$ 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 && ./compare0L'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.
printf("%s fait %d lettres\n", mot, strlen(mot));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.
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.
suggest parentheses around assignment used as truth value
Un = dans une condition, là où tu voulais == :
#include <stdio.h>
int main(void){ int essais = 0;
if (essais = 3) printf("Plus d'essais\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 (essais = 3) | ^~~~~~$ gcc -w assign.c -o assign && ./assignPlus d'essaisessais = 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 :
#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;}$ 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 bienbienUn 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 :
#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;}$ 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.
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.
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 :
| Option | Ce qu'elle ajoute |
|---|---|
-Wall | le socle : return-type, unused-variable, format, parentheses, uninitialized... |
-Wextra | unused-parameter, sign-compare, implicit-fallthrough, et d'autres |
-Wpedantic | tout ce que la norme ISO refuse, y compris les extensions de GCC |
-Werror | transforme chaque warning en erreur |
-Wconversion | les conversions implicites qui peuvent changer une valeur |
-Wshadow | une variable locale qui en masque une autre |
-Wmissing-prototypes | une fonction globale définie sans prototype visible |
-g | aucun warning : garde les noms et les lignes pour gdb et Valgrind |
Un exemple de ce que -Wconversion attrape et que -Wall -Wextra ignorent :
#include <stdio.h>
int main(void){ double moyenne = 13.75; int arrondi = moyenne;
printf("%d\n", arrondi); 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 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 :
#include <stdio.h>
int main(void){ int note = 14;
switch (note) { case 0 ... 9: printf("insuffisant\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: | ^~~~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 :
#include <stdio.h>
int main(void){ int somme;
for (int i = 1; i <= 3; i++) somme += i; printf("%d\n", somme); 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: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 :
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
