Personal tools
You are here: Home Projects Avertissements de GCC

Avertissements de GCC

by kena last modified 2011-07-09 14:17

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 si a < b la 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 return est invoquée sans argument dans une fonction dont le type de retour n'est pas void :
    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-void se termine sans instruction return :
    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.


© 2001 R. Poss

skin by PYBOOM