[FAQ] fr.comp.lang.c - partie 4/4

Guillaume Rumeau <guillaume.rumeau@wanadoo.fr>


Archive-Name: fr/comp/lang/faq-c-4

Archive-Name: fr/comp/lang/faq-c-4

---------------------------------------------------------------------------
     FAQ de fr.comp.lang.c

     18 avril 2003
     Partie 4/4 (sections 13 à 17)
---------------------------------------------------------------------------
 

13. Le pré-processeurs


13.1 Quel est le rôle du préprocesseur ?


    Le préprocesseur interprète les directives qui commencent par
    #. Principalement, ces directives permettent d'inclure
    d'autres fichiers (via #include) et de définir des
    macros (via #define) qui sont remplacées lors de la
    compilation.

    
    Chaque directive de compilation commence par un # situé
    en début de ligne (mais éventuellement précédé par des espaces,
    des tabulations ou des commentaires) et se termine en fin de
    ligne.

    
    Le préprocesseur est également responsable de la reconnaissance
    des trigraphes, des backslashs terminaux, et de
    l'ablation des commentaires.


13.2 Qu'est-ce qu'un trigraphe ?


    Dans les temps anciens, les ordinateurs n'utilisaient pas ASCII ;
    chaque machine avait son propre jeu de caractères. L'ISO a défini
    un jeu de caractères supposés présents sur toutes les machines,
    c'est l'Invariant Code Set ISO 646-1983. Ce jeu de
    caractères ne comporte pas certains caractères intéressants, tels
    que les accolades et le backslash. Aussi, le standard
    C89 a introduit les trigraphes, séquences de trois caractères
    commençant par ??.
    Il existe neuf séquences remplacées par le préprocesseur.
    Ce remplacement a lieu avant toute autre opération, et agit
    également dans les commentaires, les chaînes constantes, etc.

    
    Les trigraphes sont, de fait, rarement utilisés. On les voit
    apparaître occasionnellement et par erreur, quand on écrit ça :

    
    printf("Kikoo ??!\n");
    

    Le trigraphe est remplacé par un pipe, donc ce code
    affiche ceci :

    
    Kikoo |
    

    De toutes façons, le redoublement du point d'interrogation est de
    mauvais goût.

    
    Les compilateurs modernes soient ne reconnaissent plus les
    trigraphes, soit émettent des avertissements quand ils les
    rencontrent.


13.3 À quoi sert un backslash en fin de ligne ?


    Après le remplacement des trigraphes, le préprocesseur recherche
    tous les caractères backslash situés en fin de ligne
    ; chaque occurrence de ce caractère est supprimée, de même que le
    retour à la ligne qui le suit. Ceci permet d'unifier plusieurs
    lignes en une seule.

    
    Ce comportement est pratique pour écrire des macros ou des
    chaînes de caractères sur plusieurs lignes :

    
    printf("Hello\
     World !\n");
    

    Pour les chaînes de caractères, on peut aussi écrire plusieurs
    chaînes côte à côte, et le compilateur les unifiera (mais pas le
    préprocesseur : pour lui, ce seront deux chaînes à la suite l'une
    de l'autre). C'est une technique qui doit être généralement préférée.
    On écrira donc pour l'exemple précédent :

    
    printf("Hello"
           "World !\n");
    

13.4 Quelles sont les formes possibles de commentaires ?


    Un commentaire en C commence par /* et se termine par
    */, éventuellement plusieurs lignes plus loin. Les
    commentaires ne s'imbriquent pas.

    
    On peut aussi utiliser la compilation conditionnelle, comme ceci :

    
    #if 0
        /* ceci est ignore */
    #endif /* 0 */
    

    Dans ce cas, il faut que ce qui est ignoré soit une suite de
    tokens valide (le préprocesseur va quand même les
    regarder, afin de trouver le #endif). Ceci veut dire
    qu'il ne faut pas de chaîne non terminées. Ce genre de
    commentaire n'est pas adapté à du texte, à cause des apostrophes.

    
    La nouvelle norme du C (C99) permet d'utiliser les commentaires
    du C++ : ils commencent par // et se terminent en fin de
    ligne. Ce type de commentaire n'est pas encore supporté partout,
    donc mieux vaut ne pas s'en servir si on veut faire du code vraiment
    portable, même si avant C99, des compilateurs acceptaient ce type 
    de commentaires. 


13.5 Comment utiliser #include ?


    #include comporte trois formes principales :

    
    #include <fichier>
    #include "fichier"
    #include tokens
    

    La première forme recherche le fichier indiqué dans les
    répertoires système ; on peut les ajuster soit via des menus (dans
    le cas des compilateurs avec une interface graphique), soit en
    ligne de commande. Sur un système Unix, le répertoire
    système classique est /usr/include/. Une fois le fichier
    trouvé, tout se passe comme si son contenu était tel quel dans le
    code source, là où se trouve le #include.

    
    La deuxième forme recherche le fichier dans le répertoire
    courant. Si le fichier ne s'y trouve pas, il est ensuite cherché
    dans les répertoires systèmes, comme dans la première forme.

    
    La troisième forme, où ce qui suit le #include ne
    correspond pas à une des deux formes précédentes, commence par
    effectuer tous les remplacements de macros dans la suite de
    tokens, et le résultat doit être d'une des deux formes
    précédentes.

    
    Si le fichier n'est pas trouvé, c'est une erreur, et la
    compilation s'arrête. On notera que si on se sert d'habitude de
    #include pour inclure des fichiers d'en-tête (tels que
    stdio.h), ce n'est pas une obligation.


13.6 Comment éviter l'inclusion multiple d'un fichier ?


    Il arrive assez souvent qu'un fichier inclus en incluse un autre,
    qui lui-même en inclut un autre, etc. On peut arriver à des
    boucles, qui peuvent conduire à des redoublements de déclarations
    (donc des erreurs, pour typedef par exemple), voire des
    boucles infinies (le compilateur finissant par planter).

    
    Pour cela, le moyen le plus simple est de « protéger » chaque
    fichier par une construction de ce genre :

    
    #ifndef FOO_H_
    #define FOO_H_
    /* ici, contenu du fichier */
    #endif /* FOO_H_ */
    

    Ainsi, même si le fichier est inclus plusieurs fois, son contenu
    ne sera actif qu'une fois. Certains préprocesseurs iront même
    jusqu'à reconnaître ces structures, et ne pas lire le fichier
    si la macro de protection (ici FOO_H_) est encore
    définie.

    
    Il y a eut divers autres moyens proposés, tels que #import
    ou #pragma once, mais ils ne sont pas standards, et encore
    moins répandus.


13.7 Comment définir une macro ?


    On utilise #define. La forme la plus simple est la
    suivante :

    
    #define FOO  3 + 5
    

    Après cette déclaration, toute occurrence de l'identificateur
    FOO est remplacée par son contenu (ici 3 + 5).
    Ce remplacement est syntaxique et n'a pas lieu dans les chaînes
    de caractères, ou dans les commentaires (qui n'existent déjà plus,
    de toutes façons, à cette étape).

    
    On peut définir un contenu vide. La macro sera remplacée, en cas
    d'utilisation, par rien. Le contenu de la macro est une suite
    de tokens du C, qui n'a pas à vouloir dire quelque
    chose. On peut définir ceci, c'est valide :

    
    #define FOO  (({/coucou+}[\}+ "zap" 123
    

    La tradition est d'utiliser les identificateurs en majuscules pour
    les macros ; rien n'oblige cependant à appliquer cet usage. Les
    règles pour les identificateurs de macros sont les mêmes que pour
    celles du langage.


13.8 Comment définir une macro avec des arguments ?


    On fait ainsi :

    
    #define FOO(x, y)   ((x) + (x) * (y))
    

    Ceci définit une macro qui attend deux arguments ; notez qu'il
    n'y a pas d'espace entre le nom de la macro (FOO) et la
    parenthèse ouvrante. Toute invocation de la macro par la suite
    est remplacée par son contenu, les arguments l'étant aussi.
    Ainsi, ceci :

    
    FOO(bla, "coucou")
    

    devient ceci :

    
    ((bla) + (bla) * ("coucou"))
    

    (ce qui ne veut pas dire grand'chose en C, mais le préprocesseur
    n'en a cure). Le premier argument est remplacé deux fois, donc,
    s'il a des effets de bord (appel d'une fonction, par exemple),
    ces effets seront présents deux fois.

    
    Si la macro est invoquée sans arguments, elle n'est pas
    remplacée. Cela permet de définir une macro sensée remplacer une
    fonction, mais en conservant la possibilité d'obtenir un pointeur
    sur la fonction.
    Ainsi :

    
    int min(int x, int y) { return x < y ? x : y; }
    #define min(x, y)   ((x) < (y) ? (x) : (y))
    min(3, 4);    /* invocation de la macro */
    (min)(3, 4);  /* invocation de la fonction, via le pointeur */
    

    C'est une erreur d'invoquer une macro à argument avec un nombre
    incorrect d'arguments. En C99, on peut utiliser des arguments
    vides ; en C89, c'est flou et mieux vaut éviter.


13.9 Comment faire une macro avec un nombre variable d'arguments ?


    Ce n'est possible qu'en C99. On utilise la construction suivante :

    
    #define error(l, ...)    { \
            fprintf(stderr, "line %d: ", l); \
            fprintf(stderr, __VA_ARGS__); \
        }
    

    Ceci définit une macro, qui attend au moins un argument ; tous
    les arguments supplémentaires sont concaténés, avec leurs virgules
    de séparation, et on peut les obtenir en utilisant
    __VA_ARGS__. Ainsi, ceci :

    
    error(5, "boo: '%s'\n", bla)
    

    sera remplacé par ceci :

    
    { fprintf(stderr, "line %d: ", 5); \
      fprintf(stderr, "boo: '%s'\n", bla); }
    

    Ce mécanisme est supporté par les dernières versions de la plupart
    des compilateurs C actuellement développés ; mieux vaut l'éviter
    si le code doit aussi fonctionner avec des compilateurs un peu
    plus anciens.

    
    Il existe aussi des extensions sur certains compilateurs. Par exemple,
    sous GCC, le code suivant est équivalent à l'exemple précédent :

    
    #define error(l, format...)    { \
            fprintf(stderr, "line %d: ", l); \
            fprintf(stderr, format); \
        }
    

    Une autre méthode consiste à utiliser le parenthèsage des arguments :
    
    #define PRINTF(s) printf s
    ...
    PRINTF (("Vitesse du vent %d m/s", v));
    

13.10 Que font les opérateurs # et ## ?

    L'opérateur # permet de transformer un argument d'une
    macro en une chaîne de caractères. On fait ainsi :

    
    #define BLA(x)    printf("l'expression '%s' retourne %d\n", #x, x);
    BLA(5 * x + y);
    

    ce qui donne le résultat suivant :

    
    printf("l'expression '%s' retourne %d\n", "5 * x + y", 5 * x + y);
    

    Les éventuelles chaînes de caractères et backslashs dans
    l'argument sont protégés par des backslashes, afin de
    constituer une chaîne valide.

    
    L'opérateur ## effectue la concaténation de deux
    tokens. Si le résultat n'est pas un
    token valide, alors c'est une erreur ; mais certains
    préprocesseurs sont peu stricts et se contentent de re-séparer
    les tokens.
    On l'utilise ainsi :

    
    #define FOO(x, y)   x ## y
    FOO(bar, qux)();
    

    qui donne ceci :

    
    barqux();
    

13.11 Une macro peut-elle invoquer d'autres macros ?


    Oui. Mais il est prévu un mécanisme qui empêche les boucles
    infinies.

    
    Tout d'abord, les invocations de macros ne sont constatées que
    lors de l'utilisation de la macro, pas lors de sa définition.
    Si on fait ceci :

    
    #define FOO    BAR
    #define BAR    100
    

    alors on obtient bien 100, pas BAR.

    
    Si la macro possède des arguments, chaque fois que cet argument
    est utilisé (sans être précédé d'un # ou précédé ou suivi
    d'un ##), il est d'abord examiné par le préprocesseur,
    qui, s'il y reconnaît une macro, la remplace. Une fois les
    arguments traités, le préprocesseur les implante à leur place
    dans la suite de tokens générés par la macro, et gère
    les opérateurs # et ##.

    
    À la suite de cette opération, le résultat est de nouveau examiné
    pour rechercher d'autres remplacements de macros ; mais si une
    macro est trouvée, alors qu'on est dans le remplacement de ladite,
    cette macro n'est pas remplacée. Ceci évite les boucles infinies.

    
    Je sais, c'est compliqué. Quelques exemples :

    
    #define FOO    coucou BAR
    #define BAR    zoinx FOO
    FOO
    

    FOO est remplacée par coucou BAR, et le
    BAR résultant est remplacé par zoinx FOO. Ce
    FOO n'est pas remplacé, parce qu'on est dans le
    remplacement de FOO. Donc, on obtient coucou zoinx
    FOO.

    
    Un autre exemple, plus tordu :

    
    #define FOO(x)   x(5)
    FOO(FOO);
    

    La macro FOO est invoquée ; elle attend un argument, qui
    est FOO. Cet argument est d'abord examiné ; il y a
    FOO dedans, mais non suivi d'une parenthèse ouvrante
    (l'argument est examiné tout seul, indépendamment de ce qui le
    suit lors de son usage), donc le remplacement n'a pas
    lieu. Ensuite, l'argument est mis en place, et on obtient
    FOO(5). Ce résultat est réexaminé ; cette fois,
    FOO est bien invoquée avec un argument, mais on est dans
    le deuxième remplacement, à l'intérieur de la macro FOO,
    donc on ne remplace pas.
    Le résultat est donc : FOO(5);

    
    Si vous voulez utiliser ce mécanisme, allez lire une douzaine
    de fois la documentation du GNU cpp, et surtout le
    paragraphe 12 de l'annexe A du Kernighan &
    Ritchie (2ème édition).


13.12 Comment redéfinir une macro ?


    On peut redéfinir à l'identique une macro ; ceci est prévu pour
    les fichiers d'en-tête inclus plusieurs fois. Mais il est en
    général plus sain de protéger ses fichiers contre l'inclusion
    multiple (cf. 13.6).

    
    Redéfinir une macro avec un contenu ou des arguments différents,
    est une erreur. Certains préprocesseurs laxistes se contentent
    d'un avertissement. La bonne façon est d'abord d'indéfinir la
    macro via un #undef. Indéfinir une macro qui n'existe
    déjà pas, n'est pas une erreur.


13.13 Que peut-on faire avec #if ?

    #if permet la compilation conditionnelle. L'expression
    qui suit le #if est évaluée à la compilation, et, suivant
    son résultat, le code qui suit le #if jusqu'au prochain
    #endif, #elif ou #else est
    évalué, ou pas.
    Quand le code n'est pas évalué, les directives de préprocesseur
    ne le sont pas non plus ; mais les #if et similaires sont
    néanmoins comptés, afin de trouver la fin de la zone non compilée.

    
    Lorsque le préprocesseur rencontre un #if, il :
    
         -  récupère l'expression qui suit le #if
         -  remplace les defined MACRO et
            defined(MACRO) par la constante 1 si
            la macro nommée est définie, 0 sinon
         -  remplace les macros qui restent dans l'expression
         -  remplace par la constante 0 tous les
            identificateurs qui restent
         -  évalue l'expression
    

    L'expression ne doit comporter que des constantes entières (donc,
    éventuellement, des constantes caractères), qui sont promues au
    type (unsigned) long (en C89) ou (u)intmax_t (en
    C99). Les flottants, les pointeurs, l'accès à un tableau, et
    surtout l'opérateur sizeof ne sont pas utilisables par le
    préprocesseur.

    
    Il n'est pas possible de faire agir un #if suivant
    sizeof(long), pour reprendre un desiderata
    fréquent. Par ailleurs, les constantes de type caractère n'ont pas
    forcément la même valeur pour le préprocesseur et pour le
    compilateur.


13.14 Qu'est-ce qu'un #pragma ?


    C'est une indication pour le compilateur. Le préprocesseur
    envoie cette directive sans la modifier. Le standard C89 ne
    prévoit aucune directive standard, mais le préprocesseur
    comme le compilateur sont sensés ignorer les directives
    inconnues.

    
    Le C99 définit trois #pragma qui permettent d'ajuster
    le comportement du compilateur, quant au traitement des nombres
    flottants et complexes.


13.15 Qu'est-ce qu'un #assert ?


    C'est une extension gérée par GNU et (au moins) le
    compilateur Sun (Workshop Compiler, pour
    Solaris). C'est une sorte d'alternative à
    #ifdef, avec une syntaxe plus agréable.
    C'est à éviter, car non standard.


13.16 Comment définir proprement une macro qui comporte plusieurs statements ?

    On peut ouvrir un bloc, car tout statement est
    remplaçable, en C, par un bloc, mais cela pose des problèmes avec
    le ; terminal du statement. La manière
    recommandée est la suivante :

    
    #define foo(x)   do { f(x); printf("coucou\n"); } while (0)
    

    On peut ainsi l'utiliser comme ceci :

    
    if (bar) foo(1); else foo(2);
    

    Si on avait défini foo sans le do et le
    while (0), le code ci-dessus aurait provoqué une erreur
    de compilation, car le else serait séparé du if
    par deux statements : le bloc et le statement vide, terminé par le
    point-virgule.


13.17 Comment éviter les effets de bord ?

    Les macros peuvent être dangereuses si l'on ne fait
    pas attention aux effets de bord. Par exemple si l'on
    a le code suivant :

    
    #define MAX   3 + 5

    int i = MAX;
    

    La variable i vaudra bien 8, mais si on utilise la
    macro MAX ainsi :

    
    int i = MAX * 2;
    

    La variable i ne vaudra pas 16, mais 13. Pour éviter
    ce genre de comportement, il faut écrire la macro
    ainsi :

    
    #define MAX   (3 + 5)
    

    Dans certains cas, une macro représente une expression C
    complète. Il est alors plus cohérent de placer des parenthèses
    vides pour simuler une fonction. Et dans ce cas il ne faut pas la
    terminer par un point virgule, et

    
    #define PRTDEBUG()   (void)printf("Coucou\n")
    

    ainsi, on pourra utiliser la macro par :

    
    if (i == 10) {
       PRTDEBUG();
    }
    else {
       i++;
    }
    

    Quand une macro a des arguments il faut faire attention
    à la façon de les utiliser. Ainsi la macro :

    
    #define CALCUL(x, y)   (x + y * 2)
    

    a des effets de bord suivant la façon de l'utiliser :

    
    int i = CALCUL(3, 5);
    

    donnera bien un résultat de 13, alors que le même résultat
    serait attendu avec :

    
    int i = CALCUL(3, 2 + 3);
    

    qui donne 11. Pour éviter cela, il suffit de placer des
    parenthèses sur les arguments de la macro:

    
    #define CALCUL(x, y)   ((x) + (y) * 2)
    

    Un effet de bord qui ne peut être contourné survient quand
    la macro utilise plusieurs fois une variable :

    
    #define MAX(x, y)   ((x) > (y) ? (x) : (y))
    

    Si on utilise la macro comme cela :

    
    i = MAX(j, k);
    

    On obtiendra un résultat correct, alors qu'avec :

    
    i = MAX(j++, k++);
    

    une des variables j ou k sera incrémentée 2
    fois. Pour éviter ce genre de comportement, il faut remplacer la
    macro par une fonction, de préférence inline (C99) :

    
    inline int max(int x, int y)
    {
        return x > y ? x : y;
    }
    

    En règle générale, quand on utilise une fonction avec un nom
    en majuscule (comme MAX), on s'attend à ce que ce soit en fait
    une macro, avec les effets de bord qui en découlent. Alors que
    si le nom est en minuscule, il s'agit sûrement d'une véritable
    fonction. Cette règle n'est hélas pas générale et donc il
    convient de vérifier le véritable type d'une fonction si l'on
    ne veut pas être surpris lors de son utilisation.


13.18 Le préprocesseur est-il vraiment utile ?

    La réponse est oui. Il existe un mouvement qui voudrait faire
    disparaître le préprocesseur, en remplaçant les #define
    par des variables constantes, et avec un quelconque artifice
    syntaxique pour importer les déclarations d'un fichier d'entête.

    
    Il s'avère qu'on a vraiment besoin d'un mécanisme polymorphe
    (comme une fonction qui accepterait plusieurs types différents),
    et que seules les macros apportent ce mécanisme en C. Les
    anti-préprocesseurs acharnés parlent d'adopter le mécanisme
    des templates du C++, mais ça ne risque pas d'arriver de sitôt.

    
    Dans la vie de tous les jours, l'utilisation du préprocesseur,
    avec quelques #include et des #define sans
    surprise, ne pose pas de problème particulier, ni de maintenance,
    ni de portabilité.


13.19 Approfondir le sujet.


    Outre le Kernighan & Ritchie, qui comporte
    quelques pages très bien faites sur le sujet, on peut lire la
    documentation au format info du GNU cpp ;
    elle est assez partiale dans certains cas (condamnation explicite
    des trigraphes, par exemple) mais assez riche en enseignements.

    
    Pour le reste, chaque compilateur C vient avec un préprocesseur,
    et il existe quelques préprocesseurs indépendants (un écrit
    par Dennis Ritchie en personne, qu'on voit inclus dans
    lcc, et aussi ucpp, une oeuvre à moi :
    <http://www.di.ens.fr/~pornin/ucpp/>).


14. Fonctions de la bibliothèque


14.1 Comment convertir un nombre en une chaîne de caractères ?


    Il suffit d'utiliser sprintf().

    
    char sz[4];
    sprintf(sz, "%d", 123);
    

    Pour convertir un double ou un long,
    il faut utiliser %f ou %ld.

    
    Le problème de sprintf() est qu'il faut réserver
    assez de place pour la chaîne résultat. Ainsi le code

    
    char sz[6];
    sprintf(sz, "%u", i);
    

    marchera sur des machines où i est un entier 16 bits,
    mais il plantera si i est un entier plus grand (>
    99999).

    
    En C99, la fonction snprintf permet d'éviter les débordements :

    
    char sz[4];
    n = snprintf(sz, sizeof sz, "%d", 123);
    if (n < 0 || n >= sizeof sz)
        erreur();
    

14.2 Comment convertir une chaîne en un nombre ?


    Si le nombre espéré est un entier, il faut utiliser la fonction
    strtol(). Elle convertit une chaîne en un entier long,
    dans une base donnée.

    
    Si le nombre est un réel (float ou double),
    alors la fonction strtod() fera très bien l'affaire.

    
    char test[] = "  -123.45e+2";
    char * err = NULL;

    errno = 0;
    double result = strtod(test, &err);

    if (err == test) {
        printf("Erreur de conversion :\n");
    } 
    else if (errno == ERANGE) {
        printf("Depassement :\n");
    } 
    else {
        printf("Conversion reussie :\n");
        if(*err == '\0') {
            printf("Pour toute la chaine\n");
        }
    }
    

    Si le nombre est un long double (C99 seulement) alors la
    fonction strtold() est à préférer.


14.3 Comment découper une chaîne ?


    La fonction strtok() est faite pour ça !

    
    char sz1[] = "this is,an   example ; ";
    char sz2[] = ",; ";
    char *p;

    p = strtok(sz1, sz2);
    if (p != NULL) {
        puts(p);
        while ((p = strtok(NULL, sz2)) != NULL) {
            puts(p);
        }
    }
    

    Attention, la fonction strtok() souffre au moins des
    problèmes / caractéristiques suivants :
    
         - Elle fusionne les délimiteurs adjacents. En cas
        d'utilisation d'une virgule comme séparateur, "a,,b,c" est
        séparée en trois éléments et non quatre.
         - Le caractère du délimiteur est perdu, car il est
        remplacé par un caractère nul (0).
         - Elle modifie la chaine qu'elle analyse. C'est un
        défaut, car cela oblige à faire une copie de la chaine en cas
        d'utilisation ultérieure. Cela signifie aussi que l'on ne peut
        pas séparer une chaine littérale avec.
         - On ne peut utiliser qu'un appel de cette fonction à la
        fois. Si une séquence de strok() est en cours, et
        qu'une autre démarre, l'état de la première est perdue. Ce
        n'est pas grave pour les petits programmes, mais il est facile
        de se perdre dans la hiérarchie des fonctions imbriquées dans
        des programmes plus importants. En d'autre termes,
        strtok() brise les principes de l'encapsulation. 
   
    
    Dans des cas simples, on pourra utiliser la fonction
    strchr().


14.4 Pourquoi ne jamais faire fflush(stdin) ?


    La fonction fflush() a un comportement défini
    uniquement sur les flux ouverts en écriture tels que
    stdout. Il est possible que sur votre système, appliquer
    cette fonction à stdin soit possible, mais c'est alors
    une extension non standard. Le comportement est indéterminé, et
    imprévisible.

    
    Il faut bien comprendre que stdin n'est pas forcément
    relié au clavier, mais peut être rattaché à un réseau, un fichier,
    etc.


14.5 Comment vider le buffer associé à stdin ?


    Une bonne manière est de lire sur le flux tant qu'il n'est pas
    vide, avec les fonctions habituelles comme fgets() ou
    getchar().
    Voici un exemple avec cette dernière :

    
    c = getchar();
    if (c != '\n')
        while ( (getchar()) !=  '\n') {
    };
    

    Ce morceau de code permet de lire un caractère, et vide ce qui
    peut rester dans le buffer, notamment le '\n' final.


14.6 Pourquoi mon printf() ne s'affiche pas ?


    Le flux standard stdout, sur lequel écrit
    printf() est bufferisé. C'est à dire que les caractères
    sont écrits dans un tampon (une zone mémoire). Lorque celui-ci est
    plein, ou lorsqu'une demande explicite est faite, il est vidé dans
    le flux proprement dit (sur l'écran généralement).
    Tant que le buffer n'est pas vidé, rien ne s'affiche.

    
    Pour vider le buffer, il y a trois possibilités :

    
     -  Le buffer est plein
     -  Il est vidé explicitement par l'appel de la
    fonction fflush()
    (cf. 14.4)
     -  La chaîne de caractères se termine par un
    '\n'
    

14.7 Comment obtenir l'heure courante et la date ?


    Il faut simplement utiliser les fonctions time(),
    ctime() et/ou
    localtime(), qui contrairement à leurs noms donnent
    l'heure et la date.

    
    Voici un petit exemple :

    
    #include <stdio.h>
    #include <time.h>

    int main(void) {
        time_t now;
        time(&now);

        printf("Il est %.24s.\n", ctime(&now));

        return 0;
    }
    

14.8 Comment faire la différence entre deux dates ?


    Il faut simplement utiliser la fonction difftime().
    Cette fonction prend deux time_t en paramètres et renvoie
    un double.


14.9 Comment construire un générateur de nombres aléatoires ?


    Ce n'est pas possible.
    La bibliothèque standard inclut un générateur pseudo-aléatoire,
    la fonction rand().

    Toutefois, l'implémentation dépend du système, et celle-ci n'est
    généralement pas très bonne (en terme de résultats statistiques).
    Si rand() ne vous suffit pas (simulation numérique ou
    cryptologie), il vous faudra regarder du coté de
    bibliothèques mathématiques, dont de nombreuses se trouvent sur
    Internet. En particulier, on consultera les paragraphes 7-0 et 7-1
    des Numerical Recipes in C (cf.
    4.5) et le volume 2 de TAoCP
    (cf. 3.9).


14.10 Comment obtenir un nombre pseudo-aléatoire dans un intervalle ?


    La méthode la plus simple à faire,

    
    rand() % N
    

    qui renvoie un nombre entre 0 et N-1 est aussi
    la moins bonne.
    En effet, les bits de poids faibles ont une distribution très peu
    aléatoire. Par exemple, le bit de poids le plus faible a une
    distribution qui peut être celle-ci sur un mauvais générateur :

    0 1 0 1 0 1 0 1 0 1 ... 

    
    Voici la méthode préconisée dans Numerical Recipes
    (cf.4.5) :

    
    (int)((double)rand() / ((double)RAND_MAX + 1) * N)
    

    RAND_MAX est défini dans stdlib.h,
    et N doit être plus petit que RAND_MAX.


14.11 À chaque lancement de mon programme, les nombres pseudo-aléatoires sont toujours les mêmes ?


    C'est normal, et c'est fait exprès. Pour contrer cela, il faut
    utiliser une graine pour le générateur qui change à chaque
    lancement du programme. C'est la fonction srand() qui
    s'en charge.

    
    On peut utiliser l'heure système, avec time(), de la
    facon suivante :

    
    srand(time(NULL));
    

    Notez qu'il est peu utile d'appeler la fonction srand()
    plus d'une fois par programme.


14.12 Comment savoir si un fichier existe ?


    En C ISO, le seul moyen de savoir si un fichier existe, c'est
    d'essayer de l'ouvrir.

    
    {
       FILE *fp = fopen ("fichier.txt", "r");
    
       if (fp == NULL)
       {
          fputs ("Le fichier n'existe pas,\n"
                 "ou vous n'avez pas les droits necessaires\n"
                 "ou il est inaccessible en ce moment\n"
                 , stderr);
       }
       else
       {
          /* ... operation sur le fichier */ 
    
          fclose(fp);
       } 
    }
    

    Dans la norme POSIX, il existe la fonction
    access(), mais certains systèmes n'implémentent pas
    cette interface.


14.13 Comment connaître la taille d'un fichier ?


    Malheureusement, les fonctions stat() et
    fstat() de la norme POSIX ne sont pas
    reprises dans la norme ISO.
    La seule solution standard est d'utiliser fseek() et
    ftell().

    Toutefois, cela ne marche pas pour les très gros fichiers
    (supérieurs à LONG_MAX).


14.14 Comment lire un fichier binaire proprement ?


    Il faut ouvrir le fichier en mode « binaire », en passant la chaîne
    "rb"
    en mode d'ouverture à la fonction fopen().
    Cela évite les transformations inopportunes et les problèmes des
    caractères de contrôle.

    
    De même, pour écrire dans un fichier binaire, on utilise le mode
    "wb".


14.15 Comment marquer une pause dans un programme ?


    Il n'y a pas de fonction standard pour cela.
    Il existe toutefois la fonction sleep() en
    POSIX, elle provoque une attente passive pour une
    durée donnée en secondes.


14.16 Comment trier un tableau de chaînes ?


    La fonction qsort() est une bonne fonction de tri,
    qui implémente le Quick Sort.
    Le plus simple est de donner un exemple :

    
    /* Fonction qui compare deux pointeurs
       vers des chaines pour qsort */
    int pstrcmp(const void * p1, const void * p2){
        return strcmp(*(char * const *)p1, *(char * const *)p2);
    }
    

    Les paramètres doivent être des pointeurs génériques pour
    qsort().
   p1 et p2 sont des pointeurs sur des chaînes.
    Un tableau de chaînes doit être pris au sens d'un tableau de
    pointeurs vers des char *.

    
    L'appel à qsort() ressemble alors à :

    
    qsort(tab, sizeof tab, sizeof *tab, pstrcmp);
    

14.17 Pourquoi j'ai des erreurs sur les fonctions de la bibliothèque, alors que j'ai bien inclus les entêtes ?


    Les en-têtes (les .h) ne contiennent que les
    prototypes des fonctions.
    Le code proprement-dit de ces fonctions se trouve dans des
    fichiers objets. Ce code doit être « lié » au tien. Cela est fait
    par un éditeur de liens.

    
    Pour certaines fonctions, il faut spécifier explicitement à
    l'éditeur de liens où il peut les trouver (et ce particulièrement
    pour les fonctions non-standard).

    
    Par exemple, sous Unix, pour utiliser les fonctions mathématiques,
    il faut généralement lier le programme avec la bibliothèque adéquate :

    
    cc -lm monfic.o -o monprog
    

15. Styles


15.1 Comment bien programmer en C ?


    La chose la plus importante est de commenter un programme.
    Il ne s'agit pas de décrire en français tout ce que fait chaque
    ligne de code, mais de préciser le fonctionnement des opérations
    complexes, d'expliquer le rôle des variables, de dire à quoi
    servent les fonctions. Choisir des noms de variables et de
    fonctions explicites est une bonne façon de commenter un
    programme.

    
    Tout morceau de code qui n'est pas standard doit être abondamment
    commenté afin de rendre le portage vers d'autres cibles le moins
    fastidieux possible, l'idéal étant d'utiliser des macros.

    
    Il est également important de bien structurer son programme en
    modules, puis en fonctions.
    Certains vont jusqu'à dire qu'une fonction ne doit pas dépasser la
    taille d'un écran.

    
    Les déclarations et prototypes doivent être regroupés dans des
    fichiers d'en-têtes, avec les macros.

    
    Enfin, il est très important de bien présenter le code, avec une
    indentation judicieuse et des sauts de
    ligne. (cf. 15.2)
    Il est généralement admis que les lignes ne doivent pas dépasser
    80 caractères.

    
    Pour le reste, c'est une histoire de goût.


15.2 Comment indenter proprement du code ?


    L'indentation du code est une chose essentielle pour la
    lisibilité. Certaines personnes utilisent des tabulations, ce qui
    est une mauvaise habitude. La largeur de ces tabulations varie
    d'un éditeur à un autre. Des éditeurs remplacent les tabulation
    par un nombre d'espaces fixe et d'autres encore utilisent des
    tabulations de taille variable. Ne parlons pas des imprimantes ou
    des lecteurs de news...
    Tout ceci rend l'utilisation des tabulations difficile.

    
    Pour éviter tout problème, et améliorer la lisibilité du code,
    il faut utiliser uniquement des espaces.
    Un éditeur correct doit pouvoir générer un nombre d'espaces fixe
    (voire une indentation automatique) lorsqu'on appuye sur la touche
    <TAB> (ou autre raccourci).
    Personnellement, je règle à 4 espaces par tabulation.


15.3 Quel est le meilleur style de programmation ?


    Comme vous vous en doutez, il n'y en a pas.
    Le plus important est d'en avoir un, et de le suivre.
    Quand on utilise un type d'indentation ou un style de nommage,
    il faut l'utiliser dans tout le programme (voire dans tout ses
    programmes). C'est la régularité qui donne la lisibilité.

    
    Il existe des styles de programmations fréquemment utilisés en C,
    comme les styles K&R ou GNU.
    Le style K&R est le style « historique », et
    c'est pourquoi il est très utilisé.
    Le style GNU est utilisé pour tous les projets de la
    Free Software Fundation.

    
    Sur le site FTP <ftp://caramba.cs.tu-berlin.de>,
    le repertoire /pub/doc/style contient quelques
    documents intéressants sur la question.


15.4 Qu'est-ce que la notation hongroise ?


    C'est une convention de nommage des objets, inventée par Charles
    Simonyi. Le principe est de faire précéder le nom des
    variables par un identificateur de type.
    Par exemple, une chaîne de caractère représentant un nom sera
    nommée szname, sz signifiant « string
    zero », ou chaîne terminée par un '\0'.

    
    Personnellement, je ne trouve pas cette convention toujours
    pratique. Le nom de la variable doit avant tout refléter son rôle.


15.5 Pourquoi certains écrivent-ils if(0==x) et non if(x==0) ?


    Il arrive souvent que l'on écrive = au lieu de
    ==. Comme 0 n'est pas une lvalue
    (cf. 10.10), cette étourderie
    provoquera une erreur, simple à détecter.
    Dans le même genre, on peut écrire while (0 == x).

    
    Certains compilateurs préviennent lorsque l'on fait une
    affectation là ou est attendu un test. C'est le cas de GNU
    CC, avec l'option -Wall.

    
    Lorsque l'on écrit while ( c = fct() ), certains
    compilateurs râlent en croyant que l'on s'est trompé entre le
    = et le ==. Pour éviter cela, il suffit de
    rajouter un paire de parenthèses.

    
    while ( (c= fct()) ) {
        /* ... */
    }
    

15.6 Pourquoi faut-il mettre les '{' et '}' autour des boucles ?


    C'est une précaution contre les erreurs du genre :

    
    for(i = 0; i < N; i++);
        tab[i] = i;
    

    De plus, cela permet une plus grande simplicité dans l'évolution
    du code. En effet, les programmes ont tendance à s'épaissir avec
    le temps.


15.7 Pourquoi certains disent-ils de ne jamais utiliser les goto ?


    Le goto est une instruction qui permet de casser l'aspect
    structuré d'un programme. Des goto mal utilisés
    permettent de rendre un code totalement illisible (code
    spaghetti), d'autant plus qu'avec les structures de
    boucles traditionnelles, on peut souvent s'en passer.

    
    Toutefois, il arrive parfois que l'utilisation d'un goto
    rende le code plus propre. C'est le cas, par exemple, des sorties
    de boucles imbriquées en cas d'erreur.
    Cela rejoint le cas plus général des gestions d'exceptions
    internes.

    
    Poser comme règle de ne jamais utiliser les
    goto est une absurdité.
    Par contre, avertir le programmeur de l'utiliser avec parcimonie,
    et avec beaucoup de précautions me semble une bonne chose.


15.8 Pourquoi commenter un #endif ?

    #endif ne peut être suivi par autre chose qu'un
    commentaire. On commente donc
    pour savoir à quelle directive il correspond :

    
    #if FOO
    /* du code ou des directives */
    #ifdef BAR
    /* encore du code ou des directives */
    #endif /* BAR */
    /* encore du code ou des directives */
    #endif /* FOO */
    

15.9 Où trouver de la doc sur les différents styles ?

    Le document suivant contient des règles de base à suivre pour
    programmer proprement :
    <ftp://ftp.laas.fr/pub/ii/matthieu/c-superflu/c-superflu.pdf>

    
    Sachez également qu'il existe un programme indent
    issu de BSD qui réindente automatiquement du code,
    suivant un style donné. Les options classiques de la version
    GNU de cet utilitaire sont -kr (pour le
    style décrit dans K&R), -gnu (pour le
    style utilisé dans les projets GNU) ou encore
    -orig (pour le style BSD).

    
    Sous Unix, on trouve également la commande 
    cb avec l'option -sj pour avoir le style 
    K&R.


15.10 Comment bien structurer son programme ?


    Il n'y a pas de réponse définitive, mais voici une piste :

    
     -  Suite à l'analyse (conception) il est possible de découper
    le projet en une multitude de systèmes et de sous-systèmes.

     -  Chaque sous-système fait l'objet d'un module composé d'un
    fichier source(xxx.c) et d'un fichier d'en-tête (xxx.h).

     -  Les systèmes sont organisés hiérarchiquement (arbre) de
    façon à éviter les dépendences croisées.
    

    Evidement, tout ça est peu théorique. De plus, il existes des
    techniques avancées (ADT, callback) qui permettent les appels
    croisés propres. (c à d sans dépendances croisées). L'avantage est
    que chaque module est testable individuellement (et le plus
    souvent réutilisable).

    
    La phase suivante du développement est l'intégration. Elle consiste
    à mettre la colle qui va bien pour faire tenir tout les modules
    ensembles et à tester le fonctionnement de l'ensemble.

    
    Nous ne traitons pas ici des différentes méthodes d'analyse et de
    conception. Rappelons toutefois les 5 phases (en 'V') de
    développement d'un projet : 

    
    1 - Spécification      5 - Validation
     \                    /
      2 - Conception    4 - Integration
       \               /
        3 - Codage et test unitaire
    

    Voir à ce sujet les questions 9.3
    et 13.5.
    
    Merci à Emmanuel Delahaye pour cette réponse.


16. Autres


16.1 Comment rendre un programme plus rapide ?


    Il y a deux raisons possibles à la « lenteur » d'un programme.

    
    La première vient de l'écriture du code lui-même,
    les entrées/sorties, les allocations dynamiques,
    de nombreux appels de fonctions, des boucles, etc.
    Le programmeur a généralement peu intérêt à modifier son code,
    tout au plus pourra-t-il remplacer les petites fonctions le plus
    souvent appelées par des macros et tenter de limiter les
    boucles. On pourra aussi améliorer les entrées/sorties et les
    allocations si c'est possible. Il reste enfin les options de
    compilation sur lesquelles on peut jouer.

    
    L'autre raison vient de la complexité théorique des algorithmes
    utilisés. Dans ce dernier cas, il faut chercher un meilleur
    algorithme. Cet aspect est développé par exemple dans
    <http://www.enseignement.polytechnique.fr/profs/informatique/Jean-Jacques.Levy/poly/polyx-cori-levy.ps.gz>

    
    Il existe des outils de profilage de programmes. Il
    s'agit de compiler les sources avec une bibliothèque puis de
    lancer le programme. On lance alors un lociciel associé à la
    bibliothèque. Le résultat est un fichier où est détaillé le temps
    passé dans chaque fonction,
    le nombre d'appels, etc. Sur Unix-like,
    le projet GNU propose gprof
    (cf. 4.6).

    
    Rappelons tout de même que la vitesse d'exécution d'un programme
    (hors problèmes d'algorithmique) est peu souvent critique, et
    qu'il est bien plus important de fournir un code lisible.


16.2 Quelle est la différence entre byte et octet ?


    L'unité de mémoire élémentaire du C est le byte.
    Le byte au sens anglo-saxon et donc pas
    l'octet au sens francophone (caractère).

    
    En fait un byte correspond à un caractère non-signé
    (unsigned char), lequel peut prendre plus (ou moins) de 8
    bits. En principe, en français, on parle dans ce cas de
    multiplet (et peut-être bientôt de
    codet) comme traduction officielle de
    byte dans ce sens.

    
    (Merci à Antoine Leca).


16.3 Peut-on faire une gestion d'exceptions en C ?


    Oui, c'est possible, en utilisant le couple
    setjmp()/longjmp().

    
    #include <stdio.h>
    #include <stdlib.h>
    #include <setjmp.h>

    jmp_buf env;

    long fact(long x) {
        long i, n;

        if (x < 0)
            longjmp(env, 1);
        for (n = 1, i = 2; i <= x; i ++)
            n *= i;
        return n;
    }

    long comb(long k, long n) {
        if (k < 0 || n < 1 || k > n)
            longjmp(env, 2);
        return fact(n) / (fact(k) * fact(n - k));
    }

    int main(int argc, char *argv[]) {
        if (argc < 3) {
            fprintf(stderr, "pas assez d'arguments\n");
            return EXIT_FAILURE;
        }
        if (setjmp(env)) {
            fprintf(stderr, "erreur de calcul\n");
            return EXIT_FAILURE;
        }
        printf("%ld\n", comb(strtol(argv[1], 0, 0),
               strtol(argv[2], 0, 0)));
        return 0;
    }
    

    Voilà un programme qui calcule le coefficient binomial des deux
    arguments ; main() appelle comb() qui
    appelle fact().
    Ces fonctions vérifient un peu les arguments, et on voudrait
    renvoyer une erreur en cas de problème ; mais :

    
     -  Je renvoie déjà le résultat, il faudrait qu'il y ait des
    résultats « impossibles » pour y coder les erreurs ; c'est le cas
    ici (le résultat est toujours supérieur ou égal à 1) mais ça
    demande une analyse mathématique pas toujours facile.
     -  Je ne veux pas tester les codes d'erreurs à chaque
    invocation d'une fonction. Cela peut devenir lourd syntaxiquement,
    et ça consomme du temps CPU.
    

    setjmp() sauvegarde l'état du programme au moment de
    l'appel, et renvoie 0.
    longjmp() remplace le contenu de la pile d'exécution
    par la sauvegarde, et le programme se trouve à nouveau à l'endroit
    de l'appel à setjmp(). Celle-ci renvoie alors une
    valeur passée en paramètre de longjmp() (dans
    l'exemple, 1 pour une erreur dans fact() et
    2 pour une erreur dans comb()).

    
    La méthode présentée ici est assez rustique. Il existe des
    mécanismes de POO [5] 
    en C bien plus évolués. Vous pouvez à ce sujet
    allez voir l'excellent document :
    <http://ldeniau.home.cern.ch/ldeniau/html/oopc/oopc.html>.
    Allez voir aussi le document suivant :
    <http://cern.ch/Laurent.Deniau/html/exception/exception.html>.

    Voir aussi la question 3.10.


16.4 Comment gérer le numéro de version de mon programme ?


    Serge Paccalin propose la chose suivante :

    
    #define STR_(a) #a
    #define STR(a)  STR_(a)
    #define VERSION 4
    printf("This is version " STR(VERSION) " of the program\n");
    

    En effet, quelque chose comme :
    
    #define STR(a) #a
    #define VERSION a
    printf("This is version " STR(VERSION) " of the program\n");
    

    fait intervenir la concaténation des chaînes trop tôt ce qui fait
    que le résultat de cette dernière séquence renvoie :
    
    
    This is version VERSION of the program
    

16.5 Pourquoi ne pas mettre de `_' devant les identifiants ?

    Well well well, ce n'est pas si facile.

    
    Les vrais identificateurs réservés sont :

    
     -  les mots clés tels que if, for,
        switch, long, ...
     -  les identificateurs commençant par deux `_'
     -  les identificateurs commençant par un `_'
    suivi d'une lettre majuscule.
    

    Ensuite, il y a les headers standards et la bibliothèque. Les
    headers sont libres d'utiliser des identificateurs commençant par
    un `_' et suivis d'une lettre minuscule, comme `_liste', mais
    c'est pour définir quelque chose qui a un « file scope »,
    c'est-à-dire une portée globale à la « translation unit » (le
    fichier source C et les fichiers qu'il inclut). Donc, globalement,
    on ne doit pas s'en servir pour définir quelque chose qui a ce «
    file scope ».

    
    Ça veut dire quoi ? Que les choses suivantes sont interdites :

    
    typedef int _foo;
    struct _bar {
        int x;
        char * y;
    };
    void _f(void);
    

    En revanche, les choses suivantes sont autorisées :

    
    void f(int _x[]) {
        typedef int _foo;
        struct _bar {
            int x;
            char *y;
        };
        extern void _g(void);
    }
    struct qux {
        long _truc;
    };
    

    J'attire l'attention du public ébahi sur les quatre points
    suivants :

    
     -  En définissant des identificateurs à portée réduite (bloc,
    prototype, fonction), on peut masquer des identificateurs définis
    par les headers standards, identificateurs qui pouvaient
    intervenir dans des macros définies dans lesdits headers ; comme
    toutes les fonctions standards peuvent être surchargées par des
    macros, à l'intérieur d'un bloc où on a joué à définir un
    identificateur de type _foo, toute utilisation d'une
    facilité fournie par un header peut déclencher un « undefined
    behaviour » (par exemple, l'ordinateur utilise spontanément son
    modem pour téléphoner à la belle-mère du programmeur et l'inviter
    à venir dîner à la maison).

     -  Le extern void _g(void); explicite le fait que la
    restriction est sur le scope et pas le linkage. Bien entendu, il
    faut que la fonction _g() soit définie quelque part, avec
    un external linkage, ce qui ne peut pas se faire en C
    standard. Donc cette déclaration, quoique valide, doit avoir un
    pendant dans une autre translation unit, qui ne peut pas être
    fabriqué de façon standard. Pour compléter, rajoutons que la
    définition n'a besoin d'exister que si on se sert effectivement de
    la fonction. Donc on est en fait autorisé à faire une déclaration
    inutile qui pourrait faire planter des implémentations non
    conformes mais courantes. How useful.

     -  Quand bien même on aurait le droit, rares sont les
    implémentations complètement conformes de ce point de vue. Par pur
    pragmatisme, on évite donc de jouer avec des identificateurs
    commençant par un `_' suivi d'une lettre minuscule.

     -  Chaque header apporte ses propres restrictions ; par
    exemple, <ctype.h> peut déclarer n'importe quel
    identificateur qui commence par "is" ou "to" suivi d'une lettre
    minuscule. Ces identificateurs sont réservés pour ce qui est de
    l'external linkage, ce qui veut dire que même si on n'inclut pas
    <ctype.h>, on ne doit pas définir, entre autres, de
    variable globale "iszoinx" qui ne soit pas protégé par le mot clé
    static.
    

16.6 À quoi peut servir un cast en (void) ?


    Il y a principalement deux utilités à caster une expression en
    (void).

    
    La première utilisation est pour indiquer explicitement au
    compilateur qu'une valeur est ignorée, comme au retour d'une
    fonction. 
    Par exemple, il arrive souvent d'utiliser la fonction
    printf() sans utiliser ni même tester la valeur de
    retour.
    Ecrire l'appel à printf() ainsi

    
    (void)printf("%s\n", "Un message à la con") ;
    

    est une manière de dire au compilateur, et aux lecteurs du code,
    que je sais que printf() renvoie une valeur, mais que
    j'ai décidé de l'ignorer. Cela peut être utile pour des
    utilitaires de vérification de code, comme lint.

    
    La seconde utilisation est dans des définitions de macro.
    Voici un exemple :

    
    #undef the_truc
    #ifdef __TRUC__DEPENDANT__
    #  define the_truc(a) ((void)0)
    #else
    #  define the_truc(a) /* du code */
    #endif
    

    Ainsi, the_truc(a) est utilisable là ou une expression est
    requise, comme ici :

    
    i = (the_truc(a), 5) ;
    

    Avec une définition de the_truc comme ceci,

    
    #  define the_truc(a) 
    

    il y aurait une erreur à la compilation.


17. En guise de conclusion

--- Hou là...
--- J'ai écrit ça moi ?
--- Toujours est-il que « quelqu'un » l'a écrit.
    Tant pis, maintenant tu écris la doc'.
--- Oh non !
--- Bon alors tu débogues.
--- Ca va, ça va, j'écris la doc' ...
--- OK, ensuite tu débogues.
--- ...


---------- 
1 : Application Programming Interface     
    i.e. interface de programmation d'applications littéralement ;     
    autrement-dit bibliothèque.

2 : « Rectificatif technique ».

3 : « Amendement ».

4 : pour GCC par exemple, l'option -Wall active -Wmissing-prototypes     
    -Wmissing-declarations

5 : Programmation Orienté Objet


Valid XHTML 1.0! [Retour au sommaire] Valid CSS!

Traduit en HTML par faq2html.pl le Wed Nov 3 05:42:13 2010 pour le site Web Usenet-FR.