Cours C 7 preprocesseur modifieurs et fonctions avancees

Problème à signaler:


Télécharger Cours C 7 preprocesseur modifieurs et fonctions avancees



★★★★★★★★★★3 étoiles sur 5 basé sur 1 votes.
3 stars
1 votes

Votez ce document:

Cours C 7 Preprocesseur modifieurs et fonctions avancees

 formation informatique

Cours C 7 preprocesseur modifieurs et fonctions avancees

  Le préprocesseur

  • processus avant la co
mpilation
  • opérations brutes sur les fichiers
  • on peut voir ce que fait le préprocesseur avec: gcc -E -P fichier.c
  • $>gcc -E -P preproc.c

    int main(int argc,char* argv[]) {

    printf("%sfin","Hello world!");

    return (0);

    }

    Inclusion de fichiers

    • #include foo : inclusion du fichier désigné par foo dans le fichier courant
    • foo peut être de la forme:

    – " ... " : recherche dans le répertoire courant

    – <...> : recherche dans les répertoires d'inclusion (ceux par défaut et ceux définis par l'utilisateur)

    – possibilité de sous-répertoires:

    <sys/types.h>

    Inclusion de fichiers

    • on peut définir ses propres répertoires d'inclusion avec gcc -Idir

    $>gcc preproc.c

    preproc.c:1:22: my_stdio.h: No such file or directory

    $>gcc preproc.c -I../foo-0.1.4/include

      Inclusion de fichiers

    • chaque fichier inclus est d'abord traité par le préprocesseur
    • il est ensuite inséré là où il a été inclus

    Définition de macros

    • #define NOM texte (préférer les noms en majuscules)
    • remplace NOM par texte, sauf dans les chaînes et les identificateurs qui contiennent NOM, et seulement après le

    #define

    • utiliser des \ si texte sur plusieurs lignes:

    #define NOM debut\

    fin

    • utile pour définir des constantes

    Définition de macros

    • on peut définir autre chose que des

    constantes:

    #define SI if

    #define SINON else

    #define FOREVER for(;;)

    • on peut même ne rien mettre:

    #define const H

    _

    #define USE REGEXP

    _

      Définition de macros

    • on peut surcharger des choses existantes

    contenu de stdlib.h

    void* (*old_malloc)(size_t)=malloc; unsigned int n malloc=0;

    void* my_ malloc(size_t n) { n_malloc++;

    return old malloc(n);

    }

    int main(int argc,char* argv[]) { int* t=my_ malloc(4*sizeof(int));

    return 0;

    }

    Définition de macros

    • on peut interdire certaines fonctions

    $>gcc fseek.c -Wall -ansi

    fseek.c:8: warning: implicit declaration of function `DONT USE FSEEK BUT FSETPOS'

    _ _ _ _

    cci6baaa.o(.text+0x57):fseek. c: undefined reference to `DONT USE FSEEK BUT FSETPOS'

    _ _ _ _

    Définition de macros

    • #define nom(a,b) texte
    • définition d'une macro nom prenant deux paramètres a et b
    • pas d'espace entre nom et (
    • toujours parenthéser
    • à utiliser avec soin

     Définition de macros

    • problème de parenthésage:

    #define SQUARE(a) a*a

    int main(int argc,char* argv[]) { printf("%d\n",SQUARE(1+2)); /* 1+2*1+2 = 1+2+2 = 5 */ return 0;

    }

    • attention aux effets de bord:

    #define SQUARE(a) (a*a)

    int main(int argc,char* argv[]) {

    int i=2;

    printf("%d\n",SQUARE(i++));

    /* (i++)*(i++)=??? + i incrémenté 2 fois */

    return 0;

    }

    • #undef nom
    • permet d'annuler la définition de nom, si elle existe
    • pratique pour être sûr qu'on appelle une fonction et pas une macro

    #undef MAX

    int MAX(int a,int b) { return (a>b)?a:b; }

    • avec un # devant un paramètre, on peut obtenir une chaîne

    #define EVAL(e) printf("%s=%d\n",#e,e)

    int main(int argc,char* argv[]) {

    int i=4;

    EVAL(2*i+7);

    return 0;

    }

    $>gcc eval.c -P -E

    int main(int argc,char* argv[]) {

    int i=4;

    printf("%s=%d\n","2*i+7",2*i+7);

    return 0;

    }

    Inclusion conditionnelle

    • #ifdef nom : inclut tout jusqu'au #endif,

    #else ou #elif correspondant, SSI nom a été défini

    • #ifndef nom : SSI nom n'est pas défini
    • mécanisme utilisé pour éviter les inclusions multiples

    #ifndef CONST_H_

    #define CONST_H_

    /* ... */ #endif

    Inclusion conditionnelle

    • #if expr : inclut tout jusqu'au #endif,

    #else ou #elif correspondant, SSI expr est non nulle

    • expr ne peut contenir que des opérations

    sur des constantes entières

    #if NPLAYER==0 #define MODE DEMO #elif NPLAYER==1 #define MODE SINGLE #elif NPLAYER==2 #define MODE DUEL #else

    #define MODE MULTI #endif

     Inclusion conditionnelle

    • #if 0 : pratique pour ignorer un bout de code
    • pas de problèmes d'imbrication comme avec /* et */

      Variables du préprocesseur

    • LINE

    __ __

    • __FILE__
    • DATE

    __ __

    • TIME

    __ __

    • la date et l'heure sont celles de la compilation du fichier

    Erreurs et avertissements

    • #warning texte et #error texte

    #ifndef NPLAYERS

    #warning DEFAULT=1 PLAYER

    #elif NPLAYERS<=0 || NPLAYERS>2 #error INVALID NUMBER OF PLAYERS! #endif

    int main(int argc,char* argv[]) { /* ...*/

    return 0;

    }

    $>gcc macros.c -Wall -ansi

    macros.c:4:2: warning: #warning DEFAULT=1 PLAYER

    $>gcc macros.c -Wall -ansi -DNPLAYERS=1

    $>gcc macros.c -Wall -ansi -DNPLAYERS=4

    macros.c:6:2: #error INVALID NUMBER OF PLAYERS!

    • assert(expr); (assert.h)
    • macro qui teste si expr est nulle
    • si oui, affiche un message sur stderr et quitte le programme très brutalement avec abort
    • désactivée si rTnEBVG est définie
    • peut être utilisée pour du débogage

    #include <stdio.h> #include <assert.h>

    int div(int a,int b) { assert(b!=0);

    return a/b;

    }

    int main(int argc,char* argv[]) { printf("%d\n",div(4,0)); return 0;

    }

    $>gcc macros.c -Wall -ansi

    $>./a.out

    Assertion failed: b!=0, file macros.c, line 5

    $>gcc macros.c -Wall -ansi -DNDEBUG

    $>./a.out

    Floating point exception

    • const type nom=valeur;
    • impossible de modifier la variable nom
    • appliqué à un tableau en paramètre, empêche de modifier ses éléments
    • représailles dépendant du compilateur:

      void foo(const int t[]) { t[0]=0;

     }

    $>gcc const.c -Wall -ansi

    const.c: In function `foo':

    const.c:17: warning: assignment of read-only location

    • sur un tableau:

    – on ne peut pas modifier le contenu du tableau

    – mais on peut modifier son adresse

    void cpy(char* dest,const char* src) { while (*dest++=*src++); }

    • const permet d'éviter des bugs
    • à utiliser autant que possible!

      Classes de stockage

    • extern
    • déclare quelque chose sans le définir, ni réserver d'espace pour une fonction, ça ne fait pas de différence, mais c'est une façon de montrer qu'elle n'est pas définie dans le même fichier
    • utile pour partager une variable entre plusieurs .c

      Classes de stockage

    • même avec la protection par macros, si on met une variable dans un .h, on a des problèmes main.c

    const.h  foo.c  #include <stdio.h> #include "const.h"   

    #ifndef CONST_H_

    #define CONST_H_

    int MODE=12; #endif    extern void foo(void);

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

    return 0;

    }   

      #include "const.h"

    void foo(void) {

    MODE=MODE+5;

    $>gcc main.c foo.c -Wall -ansi

    ccs3baaa.o(.data+0x0):foo.c: multiple definition of `MODE'

    ccmqbaaa.o(.data+0x0):main.c: first defined here

      Classes de stockage

    • solution: mettre la variable dans un .c et sa déclaration extern dans le .h

     const.c

    int MODE=12;

    const.h

    #ifndef CONST_H_

    #define CONST_H_

    extern int MODE; #endif

    $>gcc main.c foo.c const.c -Wall -ansi

    Classes de stockage

    • static
    • pour une variable globale ou une fonction, indique qu'elle ne peut pas être visible de l'extérieur

    $>gcc test.c foo.c -Wall -ansi

    foo.c:1: warning: `var1' defined but not used

    foo.c:4: warning: `foo1' defined but not used

    cc8qbaaa.o(.text+0x1f):test.c: undefined reference to `foo1'

    Classes de stockage

    • avec static, initialisation par défaut à zéro
    • variable locale static = globale non

    visible de l'extérieur de la fonction

    /**

    * This function performs a given task, but no more

    * than once per second if called too often.

    */

    void temporize() {

    static time_t previous;

    time_t current;

    if ((current=time(HULL))==previous) return;

    previous=current;

    /* do the job here... */

    }

      Classes de stockage

    • register type nom;
    • demande à utiliser un registre pour la variable nom, si possible

    $>gcc z.c -Wall -ansi -DOPT=

    $>time -p a.out

    real 23.81

    user 23.66

    sys 0.02

    $>gcc z.c -Wall -ansi -DOPT=register

    $>time -p a.out

    real 12.95

    user 12.88

    sys 0.01

    Pointeurs de fonctions

    • nom d'une fonction=adresse de cette fonction
    • déclarer un pointeur de fonction:

    type_retour (*nom)(paramètres);

    #include <stdio.h>

    int main(int argc,char* argv[]) { int (*f)(const char*,...)=printf; f("%p\n",f);

    return 0;

    }

    Pointeurs de fonctions

    • on peut utiliser les pointeurs de fonctions comme n'importe quelles variables
    • exemple: void qsort(void* base,size_t n,size_t size,

    int (*compare)(const void*,const void*));

    Pointeurs de fonctions

    • on peut définir des types pointeurs de fonctions
    • exemple 1: type de printf

    typedef int(*PRINT)(const char*,...);

    int main(int argc,char* argv[]) {

    PRINT p=printf;

    int i;

    for (i=0;i<5;i++) {

    p("%d ",i);

    }

    return 0;

    }

    Pointeurs de fonctions

    • exemple 2: encodage de caractères

    typedef int(*Encoding)(int,FILE*); int utf8(int,FILE*);

    Encoding ASCII=fputc; Encoding UTF8=utf8;

    int utf8(int c,FILE* f) { /* ... */

    return c;

     }

    void print _string(Encoding e,char* s) { while (*s!='\0' && EOF!=e(*s,stdout)) { s++;

    }

    }

    Pointeurs de fonctions

    • attention aux parenthèses

    – int *f(char) : fonction prenant un char et retournant un pointeur sur un int

    – int (*f)(char) : pointeur sur une fonction prenant un char et retournant un int

    Pointeurs de fonctions

    • exemple: tableau de pointeurs de fonctions

    typedef double(*Trigo)(double);

    Trigo f[]={cos,sin,tan,acos,asin,atan};

    int main(int argc,char* argv[]) { double PI=3.14159; printf("sin(%f)=%f\n",PI,f[1](PI)); return 0;

    }

    Nombre d'arguments variable

    • comme int printf(const char*,...);
    • type et macros dans stdarg.h
    • au moins un paramètre
    • on crée une variable de type va_list qui va parcourir les paramètres
    • on l'initialise à partir du dernier paramètre connu avec va_ start

    Nombre d'arguments variable

    • on accède au prochain paramètre avec

    va_arg(va_list,type)

    • au programmeur de savoir quel est le type du prochain paramètre

    – ex: chaîne de formatage pour printf

    • quand on a fini, on doit appeler une fois (et une seule) va end

    _

    Formatage de date

    }

    va _end(args);

    Nombre d'arguments variable

    • on ne peut pas passer les ... à une autre fonction
    • mais on peut passer un va_List
    • fonctions acceptant des va_List:

    – vprintf,vfprintf,vsprintf

    – vscanf,vfscanf,vsscanf

    Personnaliser printf

    • on voudrait afficher en binaire avec des belles options comme "%02b"
    • première étape: afficher un nombre en binaire

    – parser le format

    – afficher, en respectant les règles de printf:

    • retourner le nombre de caractères affichés
    • une valeur négative en cas d'erreur

    Parser un format

    /**

    * Parses the given format string that is supposed to be of the

    * form %[ 0][n]b, where n is an integer representing the minimum number * of digits to print. Returns 0 if 'fmt' is not correct; 1 otherwise. */

    int parse_ format(const char* fmt,char *fill,int *min) {

     if (fmt==NULL || fmt[0]!='%') {

    return 0;

    }

    int i=1;

    *fill='\0';

    if (fmt[i]==' ' || fmt[i]=='0') {

    *fill=fmt[i++];

    }

    *min=0;

    if (fmt[i]>='1' && fmt[i]<='9') {

    do {

    *min=(*min)*10+fmt[i++]-'0';

    } while (fmt[i]>='0' && fmt[i]<='9');

    }

    if (fmt[i]!='b' || fmt[i+1]!='\0')

    {

    return 0;

    }

    return 1;

    }

    Afficher en binaire

    int print_bin(const char* fmt,unsigned int d) {

     char fill;

    int n;

    if (!parse _format(fmt,&fill,&n)) {

    return -1;

    }

    int n _digits=get_n_digits(d);

    int i,ret=n_digits;

    if (fill!='\0' && n_digits<n) {

    ret=n;

    for (i=0;i<n-n_digits;i++) {

    printf("%c",fill);

    }

    }

    for (i=n _digits-1;i>=0;i--) {

    printf("%c",(d&(1<<i))?'1':'0');

    }

    return ret;

    }

    Personnaliser printf

    • deuxième étape:

    – on voudrait que:

    printf("%d en decimal = ",5); print_bin("%b",5); printf(" en binaire\n");

    – puisse s'écrire de façon plus pratique:

    my printf("%d en decimal = %b en binaire\n",5,5);

    Personnaliser printf

    • solution:

    – parser soi-même le format

    – utiliser print_bin pour les formats %...b

    – laisser printf s'occuper des autres formats

    – toujours respecter les règles de retour de

    printf

    my_printf, 1/2

    int my_printf(const char* fmt,...) {

    if (fmt==HULL) return -1;

    va_list args; va_start(args,fmt);

    int i=0,n=0; /* n=number of chars printed */

    while (fmt[i]!='\0') {

    if (fmt[i]!='%') { /* case of a normal char */

    if (EOF==fputc(fmt[i],stdout)) return -1;

    n++;

    } else { /* case of a format indication %... */

    switch (fmt[++i]) {

    case '\0': return -1;

    case '%': if (EOF==fputc(fmt[i],stdout)) return -1;

    n++; break;

    default: {/* If we have '%???', we let printf do the job */

    i--; /* We get back on the '%' */

    int z=0;

    char fmt2[64];

    do {

    fmt2[z++]=fmt[i++];

    } while (z<64 && fmt2[z-1]!='\0'

    && !strchr("diouxXeEfgcspb",fmt2[z-1]));

    if (z==64) return -1;

    i--; /* We get back one character */

    if (fmt2[z-1]=='\0') return -1;

    fmt2[z]='\0';

      my_printf, 2/2

       int ret;

    switch (fmt2[z-1]) {

    case 'd': case 'i': case 'o': case 'u': case 'x': case 'X':

    {int k=va_arg(args,int); ret=printf(fmt2,k);

    break;}

    case 'e': case 'E': case 'f': case 'g':

    {double d=va_arg(args,double); ret=printf(fmt2,d);

    break;}

    case 'c':

    {char c=va _arg(args,int); ret=printf(fmt2,c);

      break;}

    case 's':

    {char* s=va _arg(args,char*); ret=printf(fmt2,s);

    break;}

    case 'p':

    {void* p=va _arg(args,void*); ret=printf(fmt2,p);

      break;}

    case 'b':

    {unsigned int value=va _arg(args,unsigned int);

    ret=print bin(fmt2,value); break;}

    _

     } }

    if (ret<0) return -1;

    n=n+ret;

    }}}i++;} va_end(args); /* A éviter, sauf quand ça ne tient pas dans la page :) */

    return n;

    Sans chaîne de formatage

    • si on suppose le type connu, on peut utiliser une valeur spéciale pour tester la fin des arguments

     /**

    * Like strcat, but optimized for multiple strings. The last

    * parameter must be NULL. 'dest' is supposed to be large enough.

    */

    void my_ strcat(char* dest,...) {

     va_list args;

    va_start(args,dest);

    dest=dest+strlen(dest);

    char* s;

    while ((s=va_arg(args,char*))!=NULL) {

    while ((*dest++=*s++));

    dest--;

     } va

    } _end(args);

    • connaître les règles des échecs ne suffit pas pour savoir y jouer
    • pour savoir vraiment programmer en C,


    578