Avertissements de GCC
Description des messages d'avertissements de GCC 2.95, le compilateur C de GNU.
Note: ce document a été écrit en 2001 pendant les études à EPITA. Les informations peuvent être obsolètes ou hors contexte.
Introduction
Suite à l'évident manque de confiance envers l'obscurité de la page man de GCC qui transparait dans les news, j'ai songé que quelques petites notes en français simple et compréhensible s'imposent.
Je vais exposer ici les différentes "options de warning" proposées par GCC, mais en me limitant à celle en rapport avec la programmation en C : je passerai donc sous silence les options relatives au C++ et à l'Objective C ; à savoir :
-Wno-import-Wtemplate-debugging-Wenum-clash-Woverloaded-virtual
Petites choses
-fsyntax-only
Demande au compilateur de parser les fichiers mais de ne pas faire de compilation proprement dite. Utile pour récupérer les warnings sans perdre de temps, pendant le codage.
Bien sûr, comme cette option inhibe la production de code compilé, il vaut mieux l'enlever au rendu ;-)
Impressions : utile, mais sans plus
-w
Inhibe complètement les warnings. Tout indiqué pour causer une crise cardiaque à l'ACU de soutenance.
Impressions : à prohiber (masochisme)
-ansi
Cette option-là n'est pas à proprement parler un flag qui permet ou non
l'affichage de certains warnings -- c'est carrément une option qui modifie le
comportement du compilateur vis-à-vis du code source. Plus précisément, ça
active deux propriétés du C ANSI qui ne sont normalement pas gérées (les
trigraphes et l'interdiction d'avoir un signe `$' dans un
identificateur), tout en désactivant les trois mots-clefs
asm, inline et typeof. En bonus, ça définit la macro
unix à 1 si vous compilez sous un unix.
Elle ne nous est pas vraiment utile, mais c'est un moyen pour nos ACUs de
vérifier qu'on n'utilise ni asm ni typeof (ni inline,
mais généralement on n'y pense pas trop, vu qu'avec
-O2, le compilateur s'occupe lui-même d'inliner les
fonctions).
Impressions : conseillé (demandé) par la norme ACU, mais pas concrètement utile.
-pedantic, -pedantic-errors
Celle-là est plus restrictive que la précédente, et impose aux programmes de suivre la norme ANSI. Toute utilisation du C non conforme à la norme ANSI provoquera un warning (qu'on reconnaît facilement car il commence par «warning: ANSI C forbids/disallows/...»).
-pedantic-errors joue le même rôle, sauf que les warnings générés
sont aussi des erreurs qui arrêtent la compilation.
Impressions : demandé par la norme ACU, plutôt indispensable quand on veut être sûr de respecter les standards, mais assez contraignant quand on veut utiliser des extensions du compilateur (mais comme ces dernières ne sont pas admises par la norme...)
-W
Génère un warning dans les cas suivants :
- pour certaines utilisations délictueuses de setjmp(3) et longjmp(3),
-
quand une fonction qui devrait renvoyer une valeur la renvoie dans certains cas
et pas d'autres ; par exemple :
int test(int a, int b) { if (a > b) return (0); }
Il est clair ici qu'il y a un problème (par exemple sia < bla valeur de retour est indéfinie). Néanmoins dans certains cas des warnings sont générés alors qu'ils n'ont pas lieu d'être, par exemple :
int test(int a, int b) { if (a > b) return (0); else exit(1); }
Dans ce cas, comme la fonction exit(3) ne se termine jamais, il n'y a pas lieu de craindre un problème. -
quand on utilise une expression composée (plusieurs expressions séparées par
des virgules), et que certaines expressions dans l'expression
composée n'ont pas d'effet sur le résultat final ; exemples :
void do_something(int, int); void toto(void) { int a; int b; a = 3, b = 4; while (a) do_something(a--, b); } --> pas de warning (les expressions de l'expression composée ont toutes un effet sur la suite) 1: void tata(void) 2: { 3: int a; 4: int b; 5: 6: for (a = 3, b = 2; b, a < 5; a++) 7: do_something(a, b), b = 3; 8: } --> 6: warning: left-hand operand of comma expression has no effect (i.e. dans "b, a < 5", invoquer l'expression "b" n'a aucun effet) mais pas de warning en ligne 7 pour un appel de fonction -- appeler une fonction peut avoir des effets de bord, donc se montrer utile dans le reste de l'évaluation. -
quand une valeur non signée est comparée avec 0 à l'aide de
>ou<=:
1: void foo(void) 2: { 3: unsigned int i; 4: 5: i = 3; 6: if (0 > i || 0 <= i) 7: do_something(); 8: } --> 6: warning: unsigned value < 0 is always 0 --> 6: warning: unsigned value >= 0 is always 1 -
quand une fonction déclare un paramètre sans type et que le compilateur se voit
obligé de lui donner le type automatique
int:
1: int toto(i) 2: { 3: return (i); 4: } --> In function `toto': --> 2: warning: type of `i' defaults to `int'
Impressions : très utile, permet surtout de détecter le code inutile et les petites erreurs d'inattention.
-Wimplicit-int
Provoque un warning quand une déclaration ne mentionne pas le type de l'objet
déclaré, et oblige le compilateur à choisir le type par défaut
int :
1: tell3();
2:
3: int add3(int a)
4: {
5: volatile i;
6:
7: i = tell3(i); /* oops... */
8: return (a + i);
9: }
10:
11: int tell3(void)
12: {
13: return (3);
14: }
Sans -Wimplicit-int:
--> 1: warning: data definition has no type or storage class
(i.e. le compilateur trouve que c'est pas cool, mais ne dit pas
grand chose, et ne détecte pas la bêtise de la ligne 7)
Avec -Wimplicit-int:
--> 1: warning: type defaults to `int' in declaration of `tell3'
--> 1: warning: data definition has no type or storage class
--> In function `add3':
--> 5: warning: type defaults to `int' in declaration of `i'
(i.e. le compilateur n'est pas content, mais en plus explique ce qui se
passe -- la correction de la déclaration de tell3() permettra par la
suite de trouver l'erreur en ligne 7)
Impressions : quasiment indispensable, parce que souvent,
quand on ne spécifie pas un type, c'est plus un oubli qu'une décision
délibérée de laisser le compilateur choisir int.
-Wimplicit-function-declaration
Provoque un warning quand une fonction est utilisée avant d'être déclarée. Dans ce cas, en effet, le compilateur génère automatiquement un prototype pour la fonction utilisée, mais on ne peut jamais être sûr que ce prototype correspond vraiment à la fonction.
Voici un exemple typique de cas où cette option est utile :
/* recopie une chaîne en supprimant les caractères 'a' */
char *copy_without_a(char *orig)
{
char *ret;
int i, j;
ret = my_strcpy(orig); /* erreur programmeur: strcpy au lieu de strdup */
for (i = 0, j = 0; orig[i]; i++)
if (orig[i] != 'a')
ret[j++] = orig[i];
return (ret);
}
Sans -Wimplicit-function-declaration:
--> In function `copy_without_a':
--> 7: warning: assignment makes pointer from integer without a cast
(i.e. il se passe quelque chose de bizarre, on ne sait pas trop quoi)
Avec -Wimplicit-function-declaration:
--> In function `copy_without_a':
--> 7: warning: implicit declaration of function `my_strcpy'
--> 7: warning: assignment makes pointer from integer without a cast
Déjà, on voit mieux. Du coup, on rajoute la déclaration de my_strcpy et
on s'aperçoit qu'il y a une erreur dans copy...(), et on voit où la corriger.
Impressions : permet, à faible coût, de détecter les mauvaises utilisations de fonctions à l'aide d'avertissements clairs. Plutôt utile.
-Wimplicit
Synonyme de -Wimplicit-int et -Wimplicit-function-delcaration
mis ensemble.
-Wmain
Génère un warning quand la fonction main est déclarée d'une manière
bizarre, c'est-à-dire différente de la manière standard. La déclaration standard
de la fonction main étant une fonction exportée (donc non statique), de
type de retour int, et prenant zéro ou deux arguments.
Exemple :
static unsigned long long main(char argc, void *argv, int toto)
{
return (0);
}
--> warning: `main' is normally a non-static function
--> warning: return type of `main' is not `int'
--> warning: first argument of `main' should be `int'
--> warning: second argument of `main' should be `char **'
--> warning: third argument of `main' should probably be `char **'
ou bien, variante du dernier message:
--> warning: `main' takes only zero or two arguments
Impressions : rarement nécessaire, car c'est rare
qu'on se loupe sur la déclaration de main, mais bon, on ne sait
jamais...
-Wreturn-type
Génère un warning dans les cas suivants :
- une fonction est définie sans type de retour, qui devient automatiquement
int, - une instruction
returnest invoquée sans argument dans une fonction dont le type de retour n'est pasvoid:
int test(int a, int b) { if (a > b) return ; return (a + b); } --> In function `test': --> 4: warning: `return' with no value, in function returning non-void - une fonction de type de retour non-
voidse termine sans instructionreturn:
int toto(void) { } --> In function `toto': --> warning: control reaches end of non-void function
Impressions : Très utile pour détecter les petits oublis ou erreurs d'inattention.
-Wunused
Génère un warning quand une variable est déclarée mais jamais utilisée, quand
une fonction déclarée static n'est jamais définie, ou quand une
instruction effectue un calcul qui n'est pas utilisé par la suite.
Exemple :
1: static int i;
2: static void tata(void);
3:
4: void toto(void)
5: {
6: int k;
7: int c;
8:
9: c + 3;
10: }
--> In function `toto':
--> 9: warning: statement with no effect
--> 6: warning: unused variable `k'
--> At top level:
--> 1: warning: `i' defined but not used
--> 2: warning: `tata' declared `static' but never defined
Impressions : Généralement utile, ça permet de connaître les bouts de code inutiles, pour pouvoir les virer (gain en nombre de lignes par exemple).
-Wswitch
Théoriquement, vu que la norme ne permet pas d'utiliser switch, nous
ne devrions pasa voir besoin d'utiliser cette option. Néanmoins, elle reste
intéressante : elle génère un warning quand une instruction switch
prend une énumération en paramètre mais ne teste pas tous les cas possibles pour
cette énumération.
Exemple :
1: enum { entier, chaine } toto;
2:
3: void foo(void)
4: {
5: switch(toto)
6: {
7: case entier:
8: break;
9: }
10: }
--> In function `foo':
--> 9: warning: enumeration value `chaine' not handled in switch
Impressions : très utile, mais que quand on a le droit d'utiliser
switch...
-Wcomment
Génère un warning quand un commentaire contient «/*», ce
qui peut arriver quand on veut désactiver
la fonction suivante :
int une_fonction(int arg)
{
/* explication sur le code */
return (arg);
}
en utilisant une mise en commentaire autour:
/*
int une_fonction(int arg)
{
/* explication sur le code */
return (arg);
}
*/
Il se trouve que le code précédent est évidemment faux ; mettons en gras le vrai commentaire :
/*
int une_fonction(int arg)
{
/* explication sur le code */
return (arg);
}
Comme les commentaires en C ne sont pas récursifs, -Wcomment est un
bon moyen de détecter ce genre de petites erreurs.
Impressions : utile quand on n'a pas de coloration syntaxique qui permet de repérer directement les débuts & fins de commentaires.
-Wtrigraphs
Génère un warning quand un trigraphe ANSI est utilisé (dans le cas où les
trigraphes sont autorisés par -ansi).
Impressions : inutile dans la plupart des cas, mais permet de détecter les cas où on veut utiliser un trigraphe potentiellement valide dans une chaîne de caractères sans vouloir son interprétation.
-Wformat
Autorise le compilateur à vérifier les arguments de printf(3), scanf(3) et fonctions apparentées, pour vérifier la correspondance entre la chaîne de format indiquée en premier paramètre et les arguments suivants.
Impressions : assez utile (pour éviter de passer un pointeur là où il devrait y avoir un entier, ou inversement par exemple), mais attention, le compilateur ne sait pas détecter toutes les combinaisons valides, et donne un message d'avertissement dans certain cas où il ne devrait pas.
-Wchar-subscripts
Avertit quand on utilise une valeur de type char comme indice pour
accéder à un tableau. Exemple :
extern int tab[];
int get_val(char i)
{
return tab[i];
}
--> In function `get_val':
--> 5: warning: array subscript has type `char'
L'intérêt de ce warning est que sur certaines machines, le type char
est signé. Par conséquent, un appel à la fonction précédente par
«get_val('\xFF')» n'accèderait pas à l'élément 255 du
tableau, mais à l'élément -1, ce qui aboutirait à un débordement mémoire
(au mieux, SIGSEGV).
Impressions : quasiment indispensable, sauf quand on est sûr de
soi. Ce qui est intéressant, c'est qu'avec un unsigned char, il
n'y a pas de warning.
-O -Wuninitialized
Génère un warning quand on utilise une variable qui n'a peut-être pas encore
reçu de valeur.
Cette option s'utilise avec (au moins) -O.
Application :
int toto(int a)
{
int b;
if (a > b)
return (a);
else
return (b);
}
--> In function `toto':
--> 3: warning: `b' might be used uninitialized in this function
Impressions : très très utile, surtout quand on manipule des pointeurs (ça permet de repérer les oublis d'initialisation). Par contre, ça peut provoquer des warnings abusifs, parce que le compilateur ne sait pas détecter tous les cas de validité correctement (par exemple dans les cas où les fonctions ne se terminent jamais). À utiliser avec modération, donc.
-Wparentheses
Génère des warnings à des endroits où le compilateur soupçonne le programmeur d'avoir fait une faute de priorité, ou encore quand la valeur d'une assignation est utilisée comme condition dans un test.
Exemple :
1: void toto(void)
2: {
3: int a, b;
4:
5: if (a = b)
6: return ;
7: if (a || b && a == b)
8: return ;
9: }
--> In function `toto':
--> 5: warning: suggest parentheses around assignment used as truth value
(i.e. le compilateur craint qu'on aie plutôt voulu dire "a == b")
--> 7: warning: suggest parentheses around && within ||
(i.e. le compilateur craint qu'on ne sache pas que && a une priorité
plus forte que ||)
Impressions : généralement recommandé, même si ça alourdit
l'écriture (on doit écrire «if ((a = b))» pour empêcher
le warning)
-Wall
Combine toutes les options -W précédentes.
En outre, si on utilise -Wall et -W ensemble,
quelques
warnings supplémentaires sont activés, dont le plus restrictif est donné par
l'exemple
suivant :
int toto(int i)
{
return (3);
}
--> In function `toto':
--> 1: warning: unused parameter `i'
Impressions : -Wall est bien utile (et demandé par
les ACUs) pour activer la plupart des warnings intéressants, mais lorsqu'on
l'utilise avec -W, il peut devenir une plaie : quand
on a (par exemple) un tableau de pointeurs sur fonctions, où chaque fonction
n'agit que sur une partie des paramètre communs à toutes, il est très
difficile de se débarasser du warning.
-Wtraditional
Génère des warnings pour certaines constructions qui ont un effet différent en C ANSI qu'en C «traditionnel». Les cas couverts par cette option sont assez obscurs (cf. page man).
Impressions : rarement nécessaire.
-Wshadow
Provoque un warning quand une déclaration écrase une autre déclaration précédente ; par exemple :
1: int i;
2:
3: void fonction(int i)
4: {
5: int i;
6:
7: for (i = 3; i < 10; i++)
8: {
9: int i;
10: i = 2;
11: }
12: }
--> 3: warning: declaration of `i' shadows global declaration
(la déclaration du paramètre écrase la déclaration globale)
--> In function `fonction':
--> 4: warning: declaration of `i' shadows global declaration
(idem)
--> 5: warning: declaration of `i' shadows a parameter
(la déclaration dans la fonction écrase la déclaration de paramètre)
--> 9: warning: declaration of `i' shadows previous local
(la déclaration locale dans la boucle écrase la déclaration de la fonction)
Impression : souvent utile quand on déclare des variables dans des macros, et qu'on passe des variables de même nom en argument à ces macros. Mais comme les macros qui déclarent des variables sont interdites par la norme, c'est aussi un bon moyen pour les ACUs de détecter les «utilisations abusives de macros». Utile aussi quand il y a des variables globales qui traînent, et qu'on ne sait plus trop leur nom.
-Wpointer-arith
Provoque des warnings lors de calculs douteux avec des pointeurs. Exemple :
1: typedef void (func_t)(int);
2: void *malloc(int);
3:
4: void foo(void *data)
5: {
6: char *str;
7: func_t *f;
8:
9: str = (char*) ++data;
10: f = malloc(sizeof (func_t));
11: }
--> In function `foo':
--> 9: warning: wrong type argument to increment
(on incrémente un pointeur sur "void", ce qui est incorrect)
--> 10: warning: sizeof applied to a function type
(une fonction n'a pas de sizeof() à proprement parler)
Impressions : indispensable. C'est un des meilleurs moyens de détecter des usage douteux de pointeurs.
-Wcast-qual
Cette option semble être activée par défaut. Provoque un warning quand on enlève des attributs d'un pointeur par transtypage. Exemples
1: void foo(void)
2: {
3: volatile int *p1;
4: int *p2;
5: const int *p3;
6: int *p4;
7: p2 = p1;
8: p4 = p3;
9: }
--> In function `foo':
--> 7: warning: assignment discards `volatile' from pointer target type
--> 8: warning: assignment discards `const' from pointer target type
Impressions : utile dans certains cas, mais assez lourd quand on veut implémenter certaines choses sans avoir droit au transtypage (cast) -- exemple : strchr(3).
-Wcast-align
Provoque un warning quand on risque de changer l'alignement d'un type. Ce cas arrive quand on transforme un pointeur sur un type de base en un pointeur sur un type de base plus large, sur les architectures où les types de base doivent être alignés..
Exemples :
void fonc(char *str)
{
long *l;
l = (long *) str;
}
--> In function `fonc':
--> 5: warning: cast increases required alignment of target type
(les pointeurs sur long doivent être multiples de sizeof(long), alors
qu'un pointeur sur char n'en a pas besoin -- l'assignation est donc
dangeureuse)
Impressions : quasiment indispensable. Une erreur d'alignement provient souvent d'un bug de programmation, et provoque quasiment toujours des plaintes du CPU (SIGBUS, SIGSEGV & Cie).
-Wwrite-strings
Fait en sorte que les chaînes de caractères soient de type const
char[taille]. Ainsi, les tentatives d'accès en écriture dans la chaîne
sont détectées. Ces tentatives sont théoriquement illégales, étant donné
que les chaînes de caractères sont stoquées dans des parties mémoire
en lecture-seule. D'où l'existence de cette option.
Exemple de code :
void foo(void)
{
char *chaine;
chaine = "tata";
}
--> In function `foo':
--> 4: warning: assignment discards `const' from pointer target type
Impressions : utile seulement si les fonctions de travail sur les chaînes sont correctement prototypées, sinon incroyablement gênant. À utiliser avec modération.
-Wconversion
Provoque un warning quand le passage d'un argument à une fonction change (relativement) beaucoup son type (i.e. la conversion nécessaire n'est pas triviale).
Exemple :
1: void fonc(double, int);
2:
3: void toto(void)
4: {
5: unsigned int i;
6:
7: fonc(3.14f, i);
8: fonc(3.14f, 2.72f);
9: fonc(3, 3);
10: }
--> In function `toto':
--> 6: warning: passing arg 2 of `fonc' as signed
due to prototype
--> 7: warning: passing arg 2 of `fonc' as integer rather than floating
due to prototype
--> 8: warning: passing arg 1 of `fonc' as floating rather than integer
due to prototype
Impressions : recommandé pour détecter quand on passe le mauvais type d'argument à une fonction. Surtout dans le cas où on risque de changer le signe d'une valeur (conversions entre valeurs signées et non signées).
-Waggregate-return
Provoque un warning quand une fonction renvoie une structure ou une union (i.e. quand la valeur de retour est d'un type non trivial).
Exemple :
struct complex { double real, imag; };
struct complex make_complex(double x, double y)
{
struct complex r;
r.real = x;
r.imag = y;
return (r);
}
--> In function `make_complex':
--> 4: warning: function returns an aggregate
Impressions : la norme interdit de renvoyer des structures, c'est donc une bonne option pour vérifier qu'on la respecte. (et même en général, renvoyer beaucoup de données en retour d'une fonction est assez coûteux)
-Wstrict-prototypes
Provoque un warning quand une fonction est déclarée ou définie sans indiquer le type de ses arguments. Par exemple :
1: void foo();
2:
3: int toto(a, b)
4: {
5: return (a + b);
6: }
--> 1: warning: function declaration isn't a prototype
--> 4: warning: function declaration isn't a prototype
Impressions : quasiment indispensable. Omettre le type des arguments d'une fonction est souvent beaucoup trop dangeureux.
-Wmissing-prototypes
Provoque un warning quand une fonction exportée (globale) est définie sans avoir été prototypée auparavant.
Exemples
static void toto(void)
{ return ; }
(pas de warning, la fonction n'est pas globale)
int toto(int, int);
int toto(int a, int b)
{ return (a + b); }
(pas de warning, la fonction est prototypée)
void machin();
void machin(int chose)
{ chose++; }
--> warning: no previous prototype for `machin'
(la fonction était déclarée, mais comme les types de ses
arguments ne sont pas spécifiés, elle n'est pas correctement)
*prototypée*)
Impressions : la norme veut que les fonctions globales soient prototypées dans des fichiers en-tête (.h) appropriés, cette option permet donc de le vérifier.
-Wmissing-declarations
Comme -Wmissing-prototypes, mais ne nécessite qu'une déclaration
(le prototype peut ne pas être complet).
Impressions : devrait être inutile en présence de
-Wmissing-prototypes... mais comme cette option est plus souple,
certains la préfèreront.
-Wredundant-decls
Provoque un warning quand une fonction est redéclarée, même quand la déclaration est identique.
Impressions : assez peu utile (voire déconcertant ou désagréable), mais peut servir quand on veut s'assurer que les fonctions ne sont déclarées chacune que dans un seul fichier en-tête (.h).
-Wnested-externs
Provoque un warning quand on importe une variable (ou fonction) globale (à
l'aide de export) depuis le corps d'une fonction.
Exemple :
void toto(void)
{
extern int i;
i = 3;
}
--> In function `toto':
--> 3: warning: nested extern declaration of `i'
Impressions : assez inutile, surtout qu'il est rare d'avoir
envie d'utiliser «extern» dans le corps d'une fonction...
-Werror
Arrête la compilation quand un warning est généré.
Impressions : généralement, les ACUs apprécient quand le code
compile avec les options de warning et -Werror.