lundi 17 mars 2014

getc macro ou fonction ?

Pour ceux qui ne liraient pas jusqu'au bout je dois encore remercier Jean-Marie Rifflet pour m'avoir suggéré cette investigation.

Sur ma machine, un MaxOSX 10.9, le manuel pour getc() dit :

The getc() function acts essentially identically to fgetc(), but is a macro that expands in-line.

Soit! Alors essayons le code suivant :

#include <stdio.h>
int main(int argc,char *argv[]) {
  printf("%p\n",&getc);
}

Normalement ceci devrait provoquer une erreur puisque getc est une macro...mais non! Ca compile et un résultat est fourni (bien sûr):

$ ./toto
0x7fff8c01e2bb
$

Bon admettons, vérifions tout de même dans stdio.h :

$ grep getc /usr/include/stdio.h
...
int getc(FILE *);
...
$

Bon là d'accord. Une chose est sûre à ce moment, le manuel est FAUX! Ce n'est pas la première fois que je tombe sur un manuel faux mais bon. Alors existe t-il une fonction sémantiquement équivalente qui soit une vraie macro-définition ?
Une piste : le manuel fait référence à getc_unlocked().
Ah évidemment! Comme toujours on oublie le cas du multithreading. getc() verrouille le flux pour éviter les accès concurrent au buffer concerné. Comme l'opération de verrouillage est longue, faire de getc une fonction n'a donc aucune importance puisque l'impact de l'appel est négligeable comparé à celui de la gestion du verrou. Du coup, on fournit getc_unlocked()! Cool! Essayons :

#include <stdio.h>
int main(int argc,char *argv[]) {
  printf("%p\n",&getc_unlocked);
}

Voilà que ça compile! Argh! Exécutons :

$ ./toto
0x7fff8c01e2bb
$

Bon essayons un appel à la fonction est laissons le compilateur faire l'expansion :

#include <stdio.h>
int main(int argc,char *argv[]) {
  printf("%p\n",&getc_unlocked);
  getc_unlocked(stdin);
}

$ gcc -E -o toto.e toto.c
$ tail -3 toto.e | head -2
 printf("%p\n",&getc_unlocked);
 (--(__stdinp)->_r < 0 ? __srget(__stdinp) : (int)(*(__stdinp)->_p++));
$

Argh! getc_unlocked() a bien une adresse mais elle est macro-étendue! Comment est-ce possible ? C'est une astuce bien maline! En fait, la fonction est déclarée :
int getc_unlocked(FILE *);
PUIS macro-définie :
#define getc_unlocked(f) ...
Ainsi lorsqu'on appelle la fonction :
getc_unlocked(stdin)
elle est nécessairement macro-étendue, mais lorsqu'une expression récupère l'adresse de la fonction
&getc_unlocked
le pré-processeur n'y voit que du feu et le compilateur retrouve l'adresse de la fonction déclarée!
De la sorte on bénéficie des deux avantages : l'optimisation de la macro-expansion mais aussi de la généricité via un pointeur sur fonction!

Reste à regarder sur les autres systèmes... Linux ? Solaris ? Je vous laisse tenter l'expérience et déterminer ce qu'il en est.

Finalement, rien de ce que l'on apprend n'est définitivement figé, encore une leçon que le temps qui passe nous apporte.

DjiBee

2 commentaires:

  1. Sur un Linux 3.8.0-19-generic, j'ai obtenu les mêmes résultats pour les fonctions de tests mais par contre quand je compile avec l'option -E voilà ce que j'obtiens pour tail -3 toto.e | head -2

    printf("%p\n",&getc_unlocked);
    getc_unlocked(stdin);

    Que cela veut-il dire?

    RépondreSupprimer
  2. Cela signifierait (il faut que je vérifie sur Linux) que sur Linux cette fonction n'est pas macro-définie mais uniquement définie en tant que fonction...

    RépondreSupprimer