🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: fr.comp.lang.c
Article #15317

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

#15317
From: Guillaume Rumeau
Date: Sun, 02 Oct 2005 09:49
1331 lines
39301 bytes
Archive-Name: fr/comp/lang/faq-c-2

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

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

     18 avril 2003
     Partie 2/4 (sections 5 � 9)
---------------------------------------------------------------------------


5. D�clarations et initialisations


5.1 Quels types utiliser ?


    Il existe en C plusieurs types de nombres entiers.
    Si vous avez � g�rer de grands nombres, il faut utiliser le type
    long. Avec la norme C99 (cf. 3.7), un type long long est disponible.
    Pour des nombres de petites tailles, et si la place m�moire
    manque, c'est le type short qu'il faut prendre.

    
    Le type char peut parfois �tre utilis� comme tr�s petit
    entier. Mais cela doit �tre �vit� au maximum. En effet, outre les
    probl�mes de signe, le code g�n�r� peut �tre plus complexe et
    risque finalement de prendre plus de place et de faire perdre du
    temps. M�me les short font parfois perdre du temps.

    
    Dans les autres cas, int est bien adapt�.

    
    Le mot cl� unsigned est � utiliser pour les nombres
    positifs, si vous avez des probl�mes dus aux d�bordements ou pour
    les traitements par bits.

    
    Le choix entre float et double ne se pose pas.
    On devrait toujours utiliser double, sauf si on a
    vraiment des contraintes de m�moire (cf. 11.1 
    et 11.10).


5.2 Comment d�finir une structure qui pointe sur elle-m�me ?


    Il y a plusieurs fa�ons correctes de le faire.
    Le probl�me est que dans la d�finition de la structure avec un
    typedef, le type n'est pas encore d�fini. Voici la
    mani�re la plus simple, o� pour contourner le probl�me, on ajoute
    un tag � la structure.

    
    typedef struct node {
        char * item;
        struct node * next;
    } node_t;
    

    Une autre solution consiste � d�clarer le type de la structure
    avant sa d�finition, avec un pointeur.

    
    typedef struct node * node_p;
    typedef struct node {
        char * item;
        node_p next;
    } node_t;
    

    Cette construction r�cursive est utilis�e pour obtenir des listes
    cha�n�es ou des arborescences.


5.3 Comment d�clarer une variable globale ?


    Sauf dans des cas pr�cis, vous devriez �viter d'utiliser des
    variables globales.

    
    Si vous y tenez vraiment, la meilleure solution est de d�clarer la
    variable dans un fichier xxx.c, et de mettre la
    d�claration extern dans un xxx.h associ�. Ceci
    �vitera des red�finitions de la variable lors des inclusions
    d'en-t�tes.

    
    Le mot cl� static permet d'avoir des variables locales
    persistantes, ce qui peut �tre une bonne alternative aux
    globales.


5.4 Quelle est la diff�rence entre const et #define ?


    Cela n'a rien � voir.
    Le #define permet de d�finir une macro, alors que le mot
    cl� const indique que l'objet qualifi� est prot�g� en
    �criture.

    
    Lors de la compilation, la premi�re phase est effectu�e par le
    pr�-processeur qui remplace les macros par leurs valeurs. C'est un
    simple copier/coller.
    Une variable d�clar�e const reste quant � elle une
    variable, mais on ne peut lui affecter une valeur que lors de
    l'initialisation. Apr�s, elle est n'est plus modifiable.

    
    Le mot-cl� const permet aussi au compilateur d'effectuer
    des optimisations en pla�ant par exemple la variable dans un
    registre.

    
    Il est � noter qu'avec la nouvelle norme C99, il est possible de
    d�clarer un tableau dont la taille est donn�e par une variable
    const. Auparavant, il fallait utiliser une macro.

    
    Voir aussi les questions 13.7 et 5.5.


5.5 Comment utiliser const avec des pointeurs ?


    Le mot-cl� const permet de prot�ger une variable de
    modifications ult�rieures. Une variable constante n'est modifiable
    qu'une fois, lors de l'initialisation.

    
    Un pointeur �tant une variable comme les autres, const
    s'utilise de la m�me fa�on.
    Voici un exemple :

    
    const char * sz1;
    char const * sz2;
    char * const sz3;
    char const * const sz4;
    

    Les variables sz1 et sz2 sont des pointeurs sur
    objet constant de type char. La variable sz3 est
    un pointeur constant sur un objet (non constant) de type
    char. Enfin, sz4 est un pointeur constant sur un
    objet constant de type char.

    
    Un petit � amusement � pour terminer, que signifie la d�claration
    const char * (* f)(char * const * s); ?

    
    Voir aussi la question 5.4.


5.6 Comment bien initialiser ses variables ?


    Les variables globales, ou d�clar�es static, sont
    initialis�es automatiquement lors de leur d�finition.
    Si aucune valeur n'est sp�cifi�e, c'est un z�ro qui est pris
    (suivant le type de la variable, 0, 0.0 ou
    NULL).

    
    Ce n'est pas le cas pour les variables automatiques (les autres).
    Il est donc n�cessaire de le faire � � la main �.

    
    Les variables allou�es dynamiquement avec malloc() ne
    le sont pas non plus.
    On pourra utiliser calloc() qui initialise les
    variables allou�es. Il est � noter que calloc() met les
    bits � 0 comme le ferait memset(). Cette
    initialisation est valide pour les types entiers (char,
    short, int et long), mais non portable
    pour les pointeurs et les flottants.

    
    Une bonne m�thode qui initialise correctement les variables
    suivant leur type est celle-ci :

    
    {
        type a[10]={0};
        struct s x ={0};
    }
    

    Avec la variante dynamique :

    
    [static] const struct s x0 ={0};
    {
        struct s *px =malloc(sizeof *px);
        *px=x0;
    }
    

5.7 Comment d�clarer un tableau de fonctions ?


    Deux cas se pr�sentent. Si toutes les fonctions ont le m�me
    prototype, il suffit de faire ainsi :

    
    extern char * f(int, int); /* une fonction                     */
    char * (*fp[N])(int, int); /* Un tableau de N fonctions        */
    char * sz;
    fp[4] = f;                 /* affectation de f dans le tableau */
    sz = fp[4](42, 12);        /* utilisation                      */
    

    Si les fonctions ont des prototypes diff�rents, il faut d�clarer
    un tableau de fonctions g�n�riques. Les fonctions g�n�riques
    n'existent pas � proprement parler. Il faut utiliser une fonction
    sans argument sp�cifi� et retournant un int.

    
    int (*fp[N])(); /* Un tableau de N fonctions quelconques*/
    

    Cela � capte � la plupart des cas, sauf les fonctions
    au nombre d'arguments variables, comme printf().

    
    Voir aussi la question 7.4.


5.8 Comment conna�tre le nombre d'�l�ments d'un tableau ?


    On peut utiliser une macro de ce type l� :
    
    #define NELEMS(n) (sizeof(n) / sizeof *(n))
          

5.9 Quelle est la diff�rence entre char a[] et char * a?


    Il faut bien se rappeler qu'en C, un tableau n'est pas un
    pointeur, m�me si � l'usage �a y ressemble beaucoup.

    
    char a[] d�clare un tableau de char de taille
    inconnue (type incomplet). D�s l'initialisation, par

    
	char a[] = "Hello";
    

    a se transforme en char[6], soit un tableau de
    six char (type complet).

    
    Il arrive souvent de voir la confusion entre � tableau � et �
    pointeur constant � (moi m�me je la fais parfois ;-) )

    
    Ce � pointeur constant � est inspir� par K&R, 1�re
    �dition. C'�tait une m�taphore malheureuse de K&R qui voulait
    exprimer qu'un tableau se comporte en g�n�ral comme
    une � valeur (rvalue) du type pointeur �, et bien entendu une
    valeur est toujours constante. Par cette m�taphore ils ont essay�
    d'expliquer pourquoi on ne pouvait pas prendre l'adresse d'un
    tableau.

    
    Malheureusement ceci n'explique pas pourquoi sizeof a
    donne la taille du tableau et non la taille d'un pointeur.

    
    Sur ce sujet, la norme est plus pr�cise en disant qu'un tableau
    dans une expression --- sauf dans &a ou
    sizeof a ou dans des initialisations par des cha�nes
    litt�rales "xyz" --- est converti automatiquement en
    une valeur du type pointeur qui pointe sur l'�l�ment initial du
    tableau (merci � Horst Kraemer pour ces pr�cisions).

    
    Voir aussi la question 7.1.


5.10 Peut-on d�clarer un type sans sp�cifier sa structure ?


    Plus pr�cis�ment, est-il possible de d�finir un type de donn�es
    dont on veut cacher � l'utilisateur de la biblioth�que
    l'impl�mentation ?

    
    Oui c'est possible, en encapsulant ce type dans une structure.
    Il y a au moins deux m�thodes. La premi�re est de d�finir une
    structure (publique) qui contient un pointeur void *, qui
    pointe vers une variable du type priv�. Ce type priv� est d�fini
    avec les fonctions, dans un .c.
    Il faut alors pr�voir des fonctions d'initialisation et de
    destruction des objets.

    
    Une autre solution consiste � d�clarer une structure qui contient
    une donn�e du type � prot�ger, et � acc�der aux types par des
    pointeurs uniquement.
    Voici un exemple :

    
    /* data.h */
    struct Data_s ;
    typedef struct Data_s Data_t ;

    extern Data_t * DataNew(int x, int y);
    extern int DataFonction(Data_t * this);

    /* data.c */
    #include <stdio.h>
    #include <stdlib.h>
    #include "data.h"

    struct Data_s {
        int x;
        int y;
    };

    Data_t * DataNew(int x , int y) {
        Data_t * pData;

        pData = malloc(sizeof *pData) ;
        if (pData) {
            pData->x = x;
            pData->y = y;
        }
        return pData;
    }

    int DataFonction(Data_t * this) {
        if (!this)
            return 0;
        return (this->x * this->x) + (this->y * this->y);
    }

    /* main.c */
    #include <stdio.h>
    #include "data.h"

    int main(void) {
        Data_t *psData;

        psData = DataNew(3, 4);
        printf("%d\n", DataFonction(psData));

        return 0;
    }
    

    (Merci � Yves Roman pour cet exemple.)


6. Structures, unions, �num�rations


6.1 Quelle est la diff�rence entre struct et typedef struct ?


    struct x1 { ... };
    typedef struct { ... } x2;
    

    La premi�re �criture d�clare un tag de structure
    x1. La deuxi�me d�clare un nouveau type nomm�
    x2.

    
    La principale diff�rence est l'utilisation.
    La deuxi�me �criture permet un peu plus l'abstraction de type.
    Cela permet de cacher le v�ritable type derri�re x2,
    l'utilisateur n'�tant pas sens� savoir que c'est une structure.

    
    struct x1 v1;
    x2 v2;
    

6.2 Une structure peut-elle contenir un pointeur sur elle-m�me ?


    Oui.

    
    Voir aussi la question 5.2.


6.3 Comment impl�menter des types cach�s (abstraits) en C ?


    L'une des bonnes fa�ons est d'utiliser des pointeurs sur des
    structures qui ne sont pas publiquement d�finies. On peut en plus
    cacher les pointeurs par des typedef.

    
    Voir aussi la question 5.10.


6.4 Peut-on passer des structures en param�tre de fonctions ?


    Oui, c'est parfaitement autoris�. Toutefois, rappelons que les
    param�tres en C sont pass�s par valeurs et copi�s dans la pile.
    Pour des grosses structures, il est pr�f�rable de passer un
    pointeur dessus, et �ventuellement un pointeur constant.


6.5 Comment comparer deux structures ?


    Il n'existe pas en C d'op�rateur ou de fonction
    pour comparer deux structures. Il faut donc le faire � la main,
    champs par champs.

    
    Une comparaison bit � bit n'est pas portable, et risque de ne pas
    marcher, en raison du padding (alignement sur
    certains octets).


6.6 Comment lire/�crire des structures dans des fichiers ?


    Il faut utiliser les fonctions fread() et
    fwrite(). Attention : les fichiers obtenus ne sont pas
    portables.

    
    Une m�thode plus portable consiste � enregistrer les structures
    dans un fichier texte.


6.7 Peut-on initialiser une union ?


    La norme pr�voit d'initialiser le premier membre d'une union.
    Pour le reste, ce n'est pas standard.
    En C99, on peut initialiser les champs d'une union :

    
    union { /* ... */ } u = { .any_member = 42 };
    

6.8 Quelle est la diff�rence entre une �num�ration et des #define ?


    Il y a peu de diff�rences.

    
    L'un des avantages de l'�num�ration est que les valeurs num�riques
    sont assign�es automatiquement.
    De plus, une �num�ration se manipule comme un type de donn�es.
    Certains programmeurs reprochent aux �num�rations de r�duire le
    contr�le qu'ils ont sur la taille des variables de type �num�ration.


6.9 Comment r�cup�rer le nombre d'�l�ments d'une �num�ration ?


    Ceci n'est possible de fa�on automatique que si les valeurs se
    suivent.

    
    typedef enum { A, B, C, D} type_e;
    

    Dans cette �num�ration, les valeurs sont donn�es par le
    compilateur dans l'ordre croissant, � partir de 0 et avec un pas de
    1. Ainsi, le nombre d'�l�ments de type_e est D + 1.

    
    On peut rajouter un �l�ment � l'�num�ration qui donne directement
    le nombre d'�l�ments :

    
    typedef enum { RED, BLUE, GREEN, YELLOW, NB_COLOR} color_e;
    

    le nombre d'�l�ments de color_e est donc NB_COLOR.

    
    Si l'on est oblig�, pour des raisons diverses et vari�es,
    de fixer d'autres valeurs aux constantes, alors cette solution ne
    marche pas. On peut toujours rajouter un champs dans l'�num�ration
    et fixer manuellement sa valeur.


6.10 Comment imprimer les valeurs symboliques d'une �num�ration ?


    On ne peut pas le faire simplement. Il faut �crire une fonction
    qui le fait. Un probl�me qui se pose alors est la maintenance, car
    une modification des valeurs de l'�num�ration entra�ne la
    n�cessit� d'une mise � jour de cette fonction.

    
    Voici un code qui limite les probl�mes de mise � jour :

    
    /* Fichier foo.itm */
    ITEM(FOO_A)
    ITEM(FOO_B)
    ITEM(FOO_C)
    /**/

    /* Fichier foo.h */
    #ifndef FOO_H
    #define FOO_H
    #define ITEM(a) a,
    typedef enum {
    #include "foo.itm"
        FOO_NB
    } foo_t;
    #undef ITEM

    #define ITEM(a) #a,
    const char * const aFoo[] = {
    #include "foo.itm"
    };
    #undef ITEM
    #endif
    /**/

    /* Fichier foo.c */
    #include <stdio.h>
    #include "foo.h"
    int main(void) {
        foo_t foo;
        for (foo = FOO_A; foo < FOO_NB; foo++) {
            printf("foo=%d ('%s')\n",foo, aFoo[foo]);
        }

        return 0;
    }
    

    Merci � Emmanuel Delahaye pour cet exemple.


7. Tableaux et pointeurs


7.1 Quelle est la diff�rence entre un tableau et un pointeur ?


    Un tableau n'est pas un pointeur.
    Un tableau est une zone m�moire pouvant contenir N
    �l�ments cons�cutifs de m�me type.
    Un pointeur est une zone m�moire qui contient l'adresse d'une
    autre zone m�moire. Toutefois, dans un grand nombre de cas, tout
    se passe comme si c'�tait la m�me chose.

    
    � ce titre, il faut bien faire la diff�rence entre a[i]
    pour un tableau et ap[i] pour un pointeur.
    Voici un exemple :

    
    char a[] = "Bonjour";
    char *ap = "Au revoir";
    

    L'expression a[3] signifie que l'on acc�de aux quatri�me
    �l�ment du tableau. ap[3] signifie que l'on acc�de � la
    zone m�moire point�e par (ap+3).
    Autrement dit, a[3] est l'objet situ� 3 places apr�s
    a[0] (a est le tableau entier),
    alors que ap[3] est l'objet situ� 3 places apr�s l'objet
    point� par ap. Dans l'exemple, a[3] vaut
    'j' et ap[3] vaut 'r'.

    
    Voir aussi la question 5.9.


7.2 Comment passer un tableau � plusieurs dimensions en param�tre d'une fonction ?


    Ce n'est pas si facile.
    La r�gle de base est qu'il faut conna�tre la taille des
    N-1 derni�res dimensions. Pour un tableau � deux
    dimensions, la deuxi�me doit �tre connue, et la fonction doit �tre
    d�clar�e ainsi :

    
    int f1(int a[][NCOLUMNS]);
                /* a est un tableau a deux dimensions (cf. remarque) */
    int f2(int (*ap)[NCOLUMNS]);
                /* ap est un pointeur sur un tableau  */
    

    Si elle n'est pas connue, il faut passer la taille du tableau en
    param�tre (ligne ET colonne) et un pointeur sur le tableau :

    
    int f(int * a, int nrows, int ncolumns);
    

    On acc�de aux �l�ments du tableau ainsi :

    
    a[i * ncolumns + j] /* element de la ieme ligne
                         * et de la jeme colonne */
    

    Une remarque :
    Dans une d�claration de param�tre

    
	int f1(int a[][NCOLUMNS])
    
    ou
    
	int f1(int a[42][NCOLUMNS])
    

    a est un pointeur sur int[NCOLUMS] malgr�
    l'�criture. La d�claration est interpr�t�e comme

    
    int (*a) [NCOLUMS]
    

    Ainsi les d�clarations de f1() et f2() dans
    l'exemple initial sont exactement les m�mes.


7.3 Comment allouer un tableau � plusieurs dimensions ?


    La premi�re solution est d'allouer un tableau de pointeurs, puis
    d'initialiser chacun de ces pointeurs par un tableau dynamique.

    
    #include <stdlib.h>

    int ** a = malloc(nrows * sizeof *a );
    for(i = 0; i < nrows; i++)
       a[i] = malloc(ncolumns * sizeof *(a[i]));
    

    Dans la vraie vie, le retour de malloc() doit �tre
    v�rifi�.

    
    Une autre solution est de simuler un tableau multi-dimensions
    avec une seule allocation :

    
    int *a = malloc(nrows * ncolumns * sizeof *a);
    

    L'acc�s aux �l�ments se fait par :

    
    a[i * ncolumns + j] /* element de la ieme ligne
	                      * et de la jeme colonne */
    

7.4 Comment d�finir un type pointeur de fonction ?


    On utilise typedef, comme pour n'importe quel autre
    type. Voici un exemple :

    
    int f(char * sz); /* une fonction                 */
    int (*pf)(char *);/* un pointeur sur une fonction */
    typedef int (*pf_t)(char *);
                      /* un type pointeur sur fonction*/
    

    Il est toutefois pr�f�rable de ne pas cacher le pointeur dans un
    typedef. La solution suivante est plus jolie :

    
    typedef int (f_t)(char *); /* un type fonction        */
    f_t * pf;                  /* un pointeur sur ce type */
    

    On l'utilise alors de cette fa�on :

    
    pf = f;
    int ret = pf("Merci pour cette reponse");
    

    Voir aussi la question 5.7.


7.5 Que vaut (et signifie) la macro NULL ?


    NULL est une macro qui repr�sente une valeur sp�ciale
    pour d�signer un pointeur nul lorsque converti au type appropri�.
    Elle est d�finie dans <stddef.h> ou dans
    <stdio.h>.

    
    La valeur r�elle de NULL est d�pendante de
    l'impl�mentation, et n'est pas n�cessairement un pointeur, ni de
    type pointeur. Des valeurs possibles sont ((void *)0) ou
    0.

    
    NULL permet de distinguer les pointeurs valides des
    pointeurs invalides. Par exemple, malloc() renvoie une
    valeur comparable � NULL quand elle �choue.

    
    Voir aussi la question 12.3.


7.6 Que signifie l'erreur � NULL-pointer assignment � ?


    Cela signifie que vous avez essay� d'acc�der � l'adresse 0 de la
    m�moire. Vous avez probablement d�r�f�renc� un pointeur
    NULL, ou oubli� de tester la valeur retour d'une
    fonction, avant de l'utiliser.


7.7 Comment imprimer un pointeur ?


    La seule mani�re pr�vue par la norme pour imprimer correctement un
    pointeur est d'utiliser la fonction printf() avec le
    code de format %p.
    Le pointeur doit �tre d'abord cast� en un pointeur
    g�n�rique void *.

    
    char * p;
    printf("Pointeur p avant initialisation: %p\n",
                                             (void *)p);
    

7.8 Quelle est la diff�rence entre void * et char * ?


    Le premier est un pointeur g�n�rique, qui peut recevoir l'adresse
    de n'importe quel type d'objet.
    Le second est un pointeur sur un caract�re, g�n�ralement utilis�
    pour les cha�nes.

    
    Avant la norme ANSI, le type void n'existait pas. C'�tait
    donc char * qui �tait utilis� pour faire des pointeurs
    g�n�riques. Depuis la norme, ce n'est plus valide.
    De nombreux programmeurs ont toutefois gard� cette habitude,
    notamment dans le cast de fonctions comme
    malloc() (cf. 12.1).


8. Cha�nes de caract�res


8.1 Comment comparer deux cha�nes ?


    Pour comparer deux cha�nes entre elles, il faut utiliser la
    fonction strcmp(), et non l'op�rateur
    ==. Celui-ci comparera les pointeurs entre eux, ce qui
    n'est probablement pas ce qui est voulu !

    
    const char * sz = "non";
    if(strcmp(sz, "oui") == 0) {
        /* sz et "oui" sont egaux */
    }
    

    Il existe aussi la fonction strncmp() qui permet de
    contr�ler la longueur de comparaison.


8.2 Comment recopier une cha�ne dans une autre ?


    Il faut utiliser la fonction strcpy(),
    et non l'op�rateur d'affectation =.
    Il faut s'assurer que l'on dispose d'espace suffisant dans la
    cha�ne cible avant d'utiliser strcpy(), qui ne fait
    aucun contr�le de d�bordement.

    
    Pour une copie plus s�curis�e, on pr�f�rera la fonction
    strncpy().


8.3 Comment lire une cha�ne au clavier ?


    Il y a de nombreuses fonctions qui le font.
    Le plus s�r est d'utiliser fgets().

    
    char tab[20];
    fgets(tab, sizeof tab, stdin);
    

    Voici un exemple complet d'utilisation propre de
    fgets() pour la lecture d'une ligne, avec un contr�le
    d'erreurs. Cette fonction est fournie par Emmanuel Delahaye.

    
    #include <stdio.h>
    #include <string.h>
    
    int get_line(char *buf, size_t size) {
       int ret; /* 0=Ok 1=Err 2=Incomplet  
                 * On peut aussi definir des constantes. (Macros, enum...)
                 */
    
       if (fgets(buf, size, stdin) != NULL) {
          char *p = strchr(buf, '\n'); /* search ... */
          if (p != NULL) {
             *p = 0; /* ... and kill */
             ret = 0;
          }
          else {
             ret = 2;
          }
       }
       else {
          ret = 1;
       }
       return ret;
    }
    
    
    Il peut �tre int�ressant dans certains cas de faire un traitement
    particulier dans le cas 2 (lecture incompl�te), comme vider le
    buffer du flux ou redimentionner la zone de r�ception (voir la question 
    14.5).

    
    La fonction gets() est � proscrire, car il n'y a aucun
    contr�le de d�bordement, ce qui peut engendrer de nombreux bugs
    (stack overflow) (cf. 8.7).


8.4 Comment obtenir la valeur num�rique d'un char (et vice-versa) ?


    En C, un char est un petit entier. Il n'y a donc aucune
    conversion � faire. Quand on a un char, on a aussi sa
    valeur, et vice-versa.


8.5 Que vaut sizeof(char) ?


    Un char vaut et vaudra toujours 1 ind�pendamment de
    l'impl�mentation. En effet, les tailles d'allocation en C se
    calculent en char (size_t). Or, un char
    a une taille de 1 char. Donc
    
    sizeof(char) == (size_t) 1
    

    Pour autant, un char ne fait pas forc�ment 8 bits (un
    octet) (voir aussi 16.2).


8.6 Pourquoi sizeof('a') ne vaut pas 1 ?


    En C, les caract�res constants sont des int, et non des
    char. Ainsi,
    
    sizeof('a') == sizeof(int)
    
    ce qui peut valoir 2 ou 4 sur votre machine, ou autre chose.


8.7 Pourquoi ne doit-on jamais utiliser gets() ?


    Serge Paccalin donne l'exemple suivant :

    
    #include <stdio.h>

    int main(void)
    {
       char chaine[16];

       printf("Tapez votre nom :\n");
       gets(chaine);
       printf("Vous vous appelez %s.\n",chaine);
       return 0;
    }
    

    Quand on tape une cha�ne de plus de 15 caract�res, rien ne va plus :
    gets() accepte sans broncher la cha�ne mais lorsqu'il
    est question de la stocker dans
    
    char chaine[16]
    

    on peut obtenir un magnifique
    Segmentation fault (core dumped).
    Dans certains cas, avec certains compilateurs
    sur certaines machines et certains OS, il est possible que �a
    passe. Mais attention ! C'est un leurre, et le changement de cible
    d�montrera la malfa�on.


8.8 Pourquoi ne doit-on presque jamais utiliser scanf() ?


    scanf() est une fonction de la biblioth�que standard, qui est 
    souvent la premi�re que l'on apprend pour lire des donn�es au clavier.
    Cette fonction n'est pas plus dangereuse qu'une autre, � condition de 
    bien savoir l'utiliser, ce qui n'est pas donn� � tout le monde.

    
    Par exemple, regardez le programme suivant donn� par Serge
    Paccalin : 

    
    #include <stdio.h>

    int main(void) {
       int val = 0;

       while (val != 1){
          printf("Tapez un nombre"
                 "(1 pour arreter le programme) :\n");
          scanf(" %d",&val);
          printf("Vous avez saisi %d.\n",val);
       }
       return 0;
    }
    

    Et il explique : � Quand le programme demande un nombre, taper
    "toto" suivi de la touche
    Entr�e. Le programme part en boucle parce que tous les
    scanf() successifs butent sur "toto" qui reste
    ind�finiment dans stdin. �

    
    Dans la plupart des cas, l'utilisation de la fonction fgets()
    sera plus simple et moins risqu�e.

    
    scanf() est une fonction qui peut �tre utilis�e dans
    certaines conditions, et � condition de bien savoir ce que l'on
    fait. L'utilisation de scanf() pour la lecture de
    nombres (entiers ou flottants) est l'une des plus acceptable. 
    Par contre, la lecture d'une cha�ne avec scanf() sans 
    contr�le de format est aussi dangereux que gets().

    
    scanf("%s", astring) ;
    

    est donc a proscrire.

    
    scanf() n'est pr�f�rable � fgets() que dans
    le cas ou l'on veut lire mot par mot et non ligne par ligne,
    et conserver le reste dans le buffer du flux
    d'entr�e. 
    Sinon, lire un mot avec fgets() ne pose pas de probl�me
    et est m�me plus simple. 

    
    scanf("%4s", astring) ;
    
    
    Cette construction, par exemple, ne pose pas les probl�mes cit�s
    plus haut (si le contr�le d'erreurs est fait), et est parfois
    utile.

    
    Voir aussi les questions 8.7 et 8.3


9. Fonctions et prototypes


9.1 Pour commencer ...


    Il y a trois notions :
    
     -  d�claration,
     -  d�finition,
     -  prototype.
    

    La d�claration d'une fonction, c'est annoncer que tel
    identificateur correspond � une fonction, qui renvoie tel type. La
    d�finition d'une fonction est une d�claration o�, en plus, on
    donne le code de la fonction elle-m�me. Le prototype est une
    d�claration de fonction o� le type des arguments est �galement
    donn�.

    
    Par exemple :
    
    int f();    /* declaration de f(), renvoyant un int, pas de prototype  */

    int f(void);/* declaration de f(), renvoyant un int, prototype (0 arg) */

    int f(void) /* definition de f() avec declaration avec prototype       */
    {
        return 42;
    }

    int f(x)    /* definition de f() avec declaration sans prototype       */
    int x;
    {
        return x;
    }

    int f()     /* definition de f() avec declaration sans prototype       */
    {
        return 42;
    }
    

    Ce qui n'est pas possible :
    
        -  avoir une d�finition sans d�claration,
        -  avoir un prototype sans d�claration.
    

    Ce qui est autoris� :
    
     -  appeler une fonction d�clar�e, qu'elle ait un prototype ou
    pas.
    

    Ce qui �tait autoris� en C90 mais ne l'est plus en C99 :
    
     -  appeler une fonction non d�clar�e ; l'appel valait
       d�claration dite � implicite �, sans prototype et avec type de
       retour int.
    

    Ce qui est encore autoris� en C99 mais dispara�tra bient�t :
    
     -  d�clarer/d�finir une fonction sans prototype.
    

    Ce qu'il faut faire quand on veut programmer lisiblement, en
    d�tectant les bugs et en gardant du code maintenable et compatible
    avec le futur :
    
     -  utiliser des d�clarations et d�finitions avec prototype.
    

9.2 Qu'est-ce qu'un prototype ?


    Un prototype est une signature de fonction.
    Comme tout objet en C, une fonction doit �tre d�clar�e avant son
    utilisation. Cette d�claration est le prototype de la fonction.
    Le prototype doit indiquer au compilateur le nom de la fonction,
    le type de la valeur de retour et le type des param�tres (sauf
    pour les fonctions � arguments variables, comme printf().
    (cf. 9.6).

    
    int fa(int a, char const * const b);
    int fb(int, char const * const);
    

    Les noms de param�tre sont optionnels, mais il est fortement
    conseill� de les laisser. Cela donne une bonne indication sur
    leurs r�les.

    
    Les fonctions de la biblioth�que ont �galement leur prototype.
    Avant l'utilisation de celles-ci, il faut inclure les fichiers
    d'en-t�te contenant les prototypes.
    Par exemple, le prototype de malloc() se trouve dans
    stdlib.h.

    
    Certains pr�f�rent ajouter le mot cl� extern au
    prototype, afin de rester coh�rent avec la d�claration des
    variables globales.

    
    Voir aussi les questions 9.3,
    12.1, 13.5 et
    14.17.


9.3 O� d�clarer les prototypes ?


    Un prototype de fonction doit �tre d�clar� avant l'utilisation de
    la fonction. Pour une plus grande lisibilit�, mais aussi pour
    simplifier la maintenance du code, il est conseill� de regrouper
    tous les prototypes
    d'un module (fichier xxx.c) dans un en-t�te 
    (<xxx.h>). Ce dernier n'a plus alors qu'� �tre inclus 
    dans le code qui utilise ces fonctions. C'est le cas des fonctions de
    la biblioth�que standard.

    
    Voir aussi les questions 13.5 et 
    9.10.


9.4 Quels sont les prototypes valides de main() ?


    La fonction main() renvoie toujours un int.
    Les prototypes valides sont :

    
    int main(void);
    int main(int argc, char * argv[]);
    

    Tout autre prototype n'est pas du tout portable et ne doit jamais
    �tre utilis� (m�me s'il est accept� par votre compilateur).

    
    En particulier, vous ne devez pas terminer la fontion
    main() sans retourner une valeur positive (non nulle en
    cas d'erreur).
    Les valeurs de retour peuvent �tre 0,
    EXIT_SUCCESS ou EXIT_FAILURE.

    
    On pourra aussi rencontrer (sous Unix) le
    prototype suivant :

    
    int main (int argc, char* argv[], char** arge);
    

    dans le but d'utiliser les variables d'environnement du
    shell actif. Ce n'est ni portable ni standard,
    d'autant plus que les fonctions getenv(),
    setenv() et putenv() le sont et suffisent
    largement.

    
    Enfin, rappelons que le prototype suivant
    
    int main () ;
    
    est parfaitement valide en C++ (et est synonyme du premier
    pr�sent� ici), mais ne l'est pas en C.


9.5 Comment printf() peut recevoir diff�rents types d'arguments ?


    printf() est une fonction � nombre variable de
    param�tres. Son prototype est le suivant :

    
    int printf(const char * format, ...); /* C 90 */
    int printf(const char * restrict format, ...); /* C 99 */
    

    Le type et le nombre des param�tres n'est pas d�fini dans le
    prototype, c'est le traitement effectu� dans la fonction qui doit
    les v�rifier.

    
    Pour utiliser cette fonction, il est donc imp�ratif d'inclure
    l'en-t�te <stdio.h>.

    
    Pour �crire une fonction de ce type, lire la question suivante
    (9.6).


9.6 Comment �crire une fonction � un nombre variable de param�tres ?


    La biblioth�que standard fournit des outils pour faciliter la
    gestion de ce type de fonctions.
    On les trouve dans l'en-t�te <stdarg.h>.

    
    Le prototype d'une fonction � nombre variable de param�tres doit
    contenir au moins un param�tre explicite, puis se termine par ...
    Exemple :

    
    int f(int nombre, ...);
    

    Il faut, d'une fa�on ou d'une autre, passer dans les param�tres le
    nombre d'arguments r�ellement transmis.
    On peut le faire en donnant ce nombre explicitement (comme
    printf()), ou passer la valeur NULL en dernier.

    
    Attention toutefois avec la valeur NULL dans ce cas.
    En effet, NULL n'est pas n�cessairement une valeur du
    type pointeur mais une valeur qui donne un pointeur
    nul si elle est affect�e ou pass�e ou compar�e � un type
    pointeur. Le passage d'une valeur � un param�tre n'est pas une
    affectation � un pointeur mais une affectation qui ob�it aux lois
    sp�ciales pour les param�tres � nombre variable (ou pour les
    param�tres d'une fonction sans prototype). Les lois de promotion
    pour les types arithm�tiques sont appliqu�es). Si NULL est
    d�fini par
    #define NULL 0
    alors (int)0 est pass� � la fonction. Si un pointeur n'a
    pas la m�me taille qu'un int ou si un pointeur nul n'est
    pas repr�sent� par � tous les bits 0 � le passage d'un 0 ne passe
    donc pas de pointeur nul. La m�thode portable est
    donc

    
    f(toto,titi,(void*)NULL);
    

    ou

    
	f(toto,titi,(void*)0);
    

    C'est le seul cas o� il faut caster NULL parce qu'il ne s'agit pas
    d'un contexte syntactique � de pointeur �, seulement d'un contexte
    � de pointeur par contrat �.

    
    Apr�s cela, les fonctions va_start(),
    va_arg() et va_end() permettent de parcourir
    la liste des param�tres.

    
    Voici un petit exemple :
    
    #include <stdarg.h>

    int vexemple(int nombre, ...){
        va_list argp;
        int i;
        int total = O;

        if(nombre < 1)
                return 0;

        va_start(argp, nombre);
        for (i = 0; i < nombre; i++) {
            total += va_arg(argp, int);
        }
        va_end(argp);

        return total;
    }
    

    Merci � Horst Kraemer pour ces remarques.


9.7 Comment modifier la valeur des param�tres d'une fonction ?


    En C, les param�tres sont pass�s par valeur. Dans la plupart des
    impl�mentations, cela se fait par une copie dans la pile.
    Lors du retour de la fonction, ces valeurs sont simplement
    d�pil�es, et les modifications �ventuelles sont perdues.
    Pour pallier cela, il faut simuler un passage des param�tres par
    r�f�rence, en passant un pointeur sur les variables � modifier.
    Voici l'exemple classique de l'�change des valeurs entre deux
    entiers :

    
    void echange(int * a, int * b) {
        int tmp = *a;
        *a = *b;
        *b = tmp;
    }
    

9.8 Comment retourner plusieurs valeurs ?


    Le langage C ne permet pas aux fonctions de renvoyer plusieurs
    objets. Une solution consiste � passer l'adresse des objets �
    modifier en param�tre.
    Une autre solution consiste � renvoyer une structure, ou un
    pointeur sur une structure qui contient l'ensemble des valeurs.
    G�n�ralement, quand on a ce genre de choses � faire, c'est qu'il
    se cache une structure de donn�es que l'on n'a pas identifi�e.
    La pire des solutions est d'utiliser des variables globales.


9.9 Peut-on, en C, imbriquer des fonctions ?

    Non, on ne peut pas.
    Les concepteurs ont jug� cela trop compliqu� � mettre en oeuvre
    (port�e des variables, gestion de la pile etc.).
    Certaines impl�mentations, comme GNU CC le supportent
    toutefois. Ceci dit, on peut tr�s bien s'en passer, en utilisant
    des pointeurs sur les structures de donn�es � partager, ou en
    utilisant des pointeurs de fonctions.


9.10 Qu'est-ce qu'un en-t�te ?
.

    Un en-t�te est un ensemble de d�clarations, d�finitions et prototypes 
    n�cessaires pour compiler et pour utiliser un module.
    Par exemple, pour utiliser les fonctions d'entr�es/sorties de la 
    biblioth�que standard, il est n�cessaire d'inclure dans son programme
    l'en-t�te <stdio.h>.

     
    Par abus, on parle souvent de fichier d'en-t�te, car historiquement, 
    et encore aujourd'hui pour de nombreuses impl�mentations, ces en-t�tes
    sont des fichiers. C'est �galement le cas pour les en-t�tes personnels.
    Toutefois, la norme n'exige pas que les en-t�tes standards soient des 
    fichiers � proprement parl�.
Technical Details
Message-ID: <fr.comp.lang.faq-c-2-1128246592.421010@calva.glou.org>
Path: arc1-ca.pugleaf.net!g2news1.google.com!news1.google.com!proxad.net!u-picardie.fr!nocworld.net!news.valadon.net!ircam.fr!fr-chartes!maintfaq

Show Full Headers
Complete Article Headers
X-Google-Thread: f9822,f6d7e7f8dee7499c,start
X-Google-Thread: 102e92,f6d7e7f8dee7499c,start
X-Google-Attributes: gidf9822,gid102e92,public
X-Google-Language: FRENCH,ASCII
Path: arc1-ca.pugleaf.net!g2news1.google.com!news1.google.com!proxad.net!u-picardie.fr!nocworld.net!news.valadon.net!ircam.fr!fr-chartes!maintfaq
From: Guillaume Rumeau <guillaume.rumeau@wanadoo.fr>
Newsgroups: fr.comp.lang.c,fr.usenet.reponses
Subject: [FAQ] fr.comp.lang.c - partie 2/4 
Supersedes: <fr.comp.lang.faq-c-2-1126950698.289705@calva.glou.org>
Followup-To: fr.comp.lang.c
Date: Sun, 2 Oct 2005 09:49:50 GMT
Organization: FCLC
Approved: fr-reponses@frmug.org
User-Agent: MaintFaq version 2.1b
Message-ID: <fr.comp.lang.faq-c-2-1128246592.421010@calva.glou.org>
Content-Type: text/plain; charset=iso-8859-15
Content-Transfer-Encoding: 8bit
Summary: Faq de fr.comp.lang.c
X-No-Productlink: yes
Lines: 1332