Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Erreur du preprocesseur

113 réponses
Avatar
candide
Bonjour,

Dans le bouquin de Ph. Drix ("Le langage C ANSI"), je trouve l'exemple
suivant pour illustrer les "substitutions réitérées" par le préprocesseur :


------------- 8< -------------------------------
#define w 0,1
#define f(x,y) (x)+(y)

f(w) /* est remplacé par (0)+(1) */
------------- >8 -------------------------------


Or, chez moi sur gcc, ça ne marche pas :

------------- 8< -------------------------------
candide@candide-desktop:~$ gcc -E test.c

test.c:4:4: erreur: macro « f » requiert 2 arguments, mais seulement 1
ont été passés
f
------------- >8 -------------------------------

Pourtant il me semble que l'exemple est valide, non ?

10 réponses

1 2 3 4 5
Avatar
Jean-Marc Bourguet
candide writes:

Bonjour,

Dans le bouquin de Ph. Drix ("Le langage C ANSI"), je trouve l'exemple
suivant pour illustrer les "substitutions réitérées" par le préprocesseur :


------------- 8< -------------------------------
#define w 0,1
#define f(x,y) (x)+(y)

f(w) /* est remplacé par (0)+(1) */
------------- >8 -------------------------------


Or, chez moi sur gcc, ça ne marche pas :

------------- 8< -------------------------------
:~$ gcc -E test.c

test.c:4:4: erreur: macro « f » requiert 2 arguments, mais seulement 1 ont
été passés
f
------------- >8 -------------------------------

Pourtant il me semble que l'exemple est valide, non ?


Non. La substitution de f se fait avant celle de ses arguments.

A+

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
candide

Pourtant il me semble que l'exemple est valide, non ?


Non. La substitution de f se fait avant celle de ses arguments.


Ah oui, d'accord, ou pour reprendre ce que dit la norme, f commence par
identifier ses arguments :


6.10.3.1 Argument substitution
1 After the arguments for the invocation of a function-like macro have
been identified, argument substitution takes place.


et ici, il n'y a pas coïncidence.


Je trouve que l'algorithme de preprocess et en particulier d'expansion
de macros n'est pas clairement expliqué en général.


Au passage, Harbison & Steele donne l'exemple suivant (§3.3.3 pages 50-51) :

------------------------ 8< ------------------------
#define plus(x,y) add(y,x)
define add(x,y) ((x)+(y))

plus(plus(a,b),c)
------------------------ >8 ------------------------

et il indique que les étapes d'expansion sont les suivantes :


------------------------ 8< ------------------------
1. plus(plus(a,b),c)
2. add(c,plus(a,b))
3. ((c)+(plus(a,b)))
4. ((c)+(add(b,a)))
5. ((c)+(((b)+(a))))
------------------------ >8 ------------------------



Or, vu

------------------------
6.10.3.1 Argument substitution
A parameter in the replacement list, unless (...) is
replaced by the corresponding argument after all macros contained
therein have been expanded. Before being substituted, each argument's
preprocessing tokens are completely macro replaced [as if they formed
the rest of the preprocessing file; no other preprocessing tokens are
available.]
------------------------

(dont je n'ai pas complètement compris la partie que j'ai placée entre
crochets à la fin de l'extrait)


moi j'avais compris que les étapes étaient les suivantes :

1. plus(plus(a,b),c)
2. plus(add(a,b),c)
3. plus(((b)+(a)),c)
4. add(c,((b)+(a))) /* rescanning, cf. § 6.10.3.4 */
5. ((c)+(((b)+(a))))


Qu'en pensez-vous ?


Avatar
espie
In article <47e26470$0$20187$,
candide wrote:

Je trouve que l'algorithme de preprocess et en particulier d'expansion
de macros n'est pas clairement expliqué en général.


D'abord, c'est effectivement tordu. Ensuite, c'est un des elements qui a
le plus change, d'abord dans la norme, mais aussi dans le suivi de la
norme au niveau des compilateurs.

Ces jours-ci, je conseillerais de limiter tres tres fortement l'utilisation
du preprocesseur... il conduit tres souvent a des problemes complexes
a debugguer.

Je ne m'en sers plus guere que pour des definitions de constante, et quelques
tests a la #ifdef, en fait.

L'existence et le support de inline permettent avantageusement de s'en passer,
et d'avoir un code qui est verifie par le compilateur, ce qui est toujours
un plus.

Tres souvent, lorsqu'on se dit que le preprocesseur serait pratique, c'est
quand on fait des acrobaties avec du code polymorphe ou assimile, et alors,
c'est quand meme infiniment moins casse-gueule en C++...

Avatar
Antoine Leca
En news:47e26470$0$20187$, candide va escriure:
Je trouve que l'algorithme de preprocess et en particulier d'expansion
de macros n'est pas clairement expliqué en général.


Par qui ? par les ouvrages d'enseignement du C : àmha, c'est parfaitement
voulu, car il est vain de vouloir écrire du code complètement portable si tu
joues avec cela : ce domaine a mis du temps à mûrir, donc les compilateurs
ont mis beaucoup de temps à se stabiliser, donc si tu joues trop sur les
bords tu vas trouver des cas où les compilateurs diffèrent, autrement dit le
code n'est pas portable. Comme le gain réel est faible ou nul (car le code
est difficile à comprendre pour un humain), ce n'est pas enseigné.



Qu'en pensez-vous ?


D'abord et surtout que le résultat est le même !

Ensuite que les principaux préprocesseurs conformes semblent à première vue
pencher pour l'interprétation de MM. Harbison & Steele, mais je ne suis pas
assez calé en préprocesseur pour être capable d'expliquer clairement
pourquoi. Ce qui me paraît clair, c'est que l'interprétation précise demande
l'intervention de spécialistes, donc tout code qui essaye de jouer au plus
fin est susceptible de rencontrer des bogues dans les implémentations, et
par conséquent un bon programmeur devrait éviter de jouer à cela ; ce qui
rejoint l'analyse ci-dessus.


Antoine

Avatar
espie
In article <fsanll$nec$,
Antoine Leca wrote:
Par qui ? par les ouvrages d'enseignement du C : àmha, c'est parfaitement
voulu, car il est vain de vouloir écrire du code complètement portable si tu
joues avec cela : ce domaine a mis du temps à mûrir, donc les compilateurs
ont mis beaucoup de temps à se stabiliser, donc si tu joues trop sur les
bords tu vas trouver des cas où les compilateurs diffèrent, autrement dit le
code n'est pas portable. Comme le gain réel est faible ou nul (car le code
est difficile à comprendre pour un humain), ce n'est pas enseigné.


Je rajouterai: lorsque j'enseigne le C, il y a des constructions que
j'apprend aux etudiants a utiliser, et d'autres constructions que je
leur apprend a lire.

Je garde generalement le preprocesseur pour assez tard, et je leur donne
les fonctions inline en meme temps. J'explique pourquoi le preprocesseur
est une mauvaise idee dans enormement de cas, et je donne les rares cas
d'utilisation `raisonnables' (voire indispensables):
- les constantes (dommage que C n'ait pas emprunte a C++ en la matiere)
- les #ifdef raisonnes (en expliquant la necessite de faire un niveau
d'indirection supplementaire pour avoir des #ifdef `feature-based' pour
les problemes de portabilite).
- quelques cas tres specifiques, dont __FILE__, __func__ (pas preprocesseur,
mais bon), et assert.
- #if 0 comme facon simple de commenter rapidement un bloc de code.
- les definitions de champs `simplifies' pour les union.

Pour le reste, je leur montre des exemples de code reel... le plus souvent
suffisamment moche pour ne pas donner envie.

Au niveau professionnel, je dois tres souvent jongler avec le preprocesseur
pour gerer du code existant. Ca n'est jamais drole, et tres souvent
terriblement casse-tete...

Avatar
candide
En news:47e26470$0$20187$, candide va escriure:
Je trouve que l'algorithme de preprocess et en particulier d'expansion
de macros n'est pas clairement expliqué en général.


Par qui ? par les ouvrages d'enseignement du C :


Oui

àmha, c'est parfaitement
voulu, car il est vain de vouloir écrire du code complètement portable si tu
joues avec cela :


Les ouvrages de C sont peu loquaces et surtout peu précis concernant le
fonctionnement du préprocesseur. Et je n'ai jamais qu'un argument de non
portabilité ait été avancé (je viens encore de re-parcourir ce qu'en dit
K&R : aucun argument de cet ordre).

ce domaine a mis du temps à mûrir, donc les compilateurs
ont mis beaucoup de temps à se stabiliser, donc si tu joues trop sur les
bords tu vas trouver des cas où les compilateurs diffèrent, autrement dit le
code n'est pas portable.


Je comprends pas : si les préprocesseurs sont conformes à la norme, il
ne devrait pas y avoir de différence. Où alors, la norme serait-elle
ambiguë sur l'algorithme d'expansion de macro (puisque c'était ça le
problème initial).

Comme le gain réel est faible ou nul (car le code
est difficile à comprendre pour un humain), ce n'est pas enseigné.



Je remplace ma question dans son contexte. Je lis à mes heures perdues
le livre de Plauger "The C Standard Library", j'aime beaucoup sa façon
de coder et je me dis que je pourrais apprendre pas mal de choses en le
lisant. Je regarde son implémentation de la macro assert et c'est là que
je tombe sur quelque chose que je n'avais jamais vu (alors que a
posteriori semble-t-il c'est assez classique dans la pratique des
codeurs C), en résumant, voici l'astuce :

#STR(x) VAL(x)
#VAL #x
/* blabla */ STR(__LINE__) /* blabla */

et voilà pourquoi, j'ai eu besoin de mieux comprendre comment fonctionne
l'expansion d'une macro à paramètre .

Le but ici est de NE PAS avoir "__LINE__". J'essaye d'appliquer ce que
je comprends de la norme :


----------------- 8< -------------------------------------
6.10.3.1 Argument substitution
1 After the arguments for the invocation of a function-like macro have
been identified, argument substitution takes place. A parameter in the
replacement list, unless preceded by a # or ## preprocessing token or
followed by a ## preprocessing token (see below), is replaced by the
corresponding argument after all macros contained therein have been
expanded.
----------------- >8 -------------------------------------

Donc, les arguments de la macro étant identifiés (cf. ce que disait
JMB), les étapes sont :

__LINE__ est remplacé par sa "valeur", disons 15 pour fixer les idées.
ensuite STR(__LINE__) est ré-écrit VAL(15). Ensuite, on doit invoquer
le rescanning (6.10.3.4 Rescanning and further replacement), ce qui
donne le résultat attendu.




Ce n'est pas tellement que je tienne à l'utiliser mais dans la
pratique, ça l'est assez, il me semble me rappeler que Laurent Boyer
avait écrit une bibliothèque pour faire de la programmation générique
basée sur un ensemble de macros.


D'abord et surtout que le résultat est le même !



Oui !

Ensuite que les principaux préprocesseurs conformes semblent à première vue
pencher pour l'interprétation de MM. Harbison & Steele, mais je ne suis pas
assez calé en préprocesseur pour être capable d'expliquer clairement
pourquoi.



Si TOI tu n'es pas assez calé, alors, sans flagornerie, il va pas rester
beaucoup de monde qui va l'être. C'est si compliqué que ça ? Pourtant la
norme ne me semble pas spécialement plus dure (!) à lire là qu'ailleurs.


Ce qui me paraît clair, c'est que l'interprétation précise demande
l'intervention de spécialistes, donc tout code qui essaye de jouer au plus
fin


Euh franchement, je ne pensais pas à jouer au plus fin en extrayant un
bout de code de H&S ...


Avatar
candide


Je rajouterai: lorsque j'enseigne le C, il y a des constructions que
j'apprend aux etudiants a utiliser, et d'autres constructions que je
leur apprend a lire.



Oui, je pense que c'est une bonne chose car souvent on a besoin de
savoir lire ( = déchiffrer) du code sans qu'on ait besoin d'écrire
nous-même du code de la même façon. Un truc qui manque dans les bouquins
de C, de l'analyse de code comme en français on fait de l'analyse de texte.



Je garde generalement le preprocesseur pour assez tard, et je leur donne
les fonctions inline en meme temps. J'explique pourquoi le preprocesseur


Je ne connais pas C99.


est une mauvaise idee dans enormement de cas, et je donne les rares cas
d'utilisation `raisonnables' (voire indispensables):
- les constantes (dommage que C n'ait pas emprunte a C++ en la matiere)


et les enums ?

- les #ifdef raisonnes (en expliquant la necessite de faire un niveau
d'indirection supplementaire pour avoir des #ifdef `feature-based' pour
les problemes de portabilite).


Là je sais pas trop de quoi tu parles.

- quelques cas tres specifiques, dont __FILE__, __func__ (pas preprocesseur,
mais bon), et assert.
- #if 0 comme facon simple de commenter rapidement un bloc de code.
- les definitions de champs `simplifies' pour les union.


Ça me dit rien comme ça.


Pour le reste, je leur montre des exemples de code reel... le plus souvent
suffisamment moche pour ne pas donner envie.



Je ne cherche pas particulièrement à te contredire --et je te répète que
j'aurais plutôt tendance à faire confiance en ton expérience -- ,
simplement ce n'est pas de ma faute si c'est le code du réel qui
m'impose l'emploi du préprocesseur, moi programmeur du dimanche, j'ai
toutes les raisons de croire ce que font des codeurs professionnels.

Avatar
espie
In article <47e98121$0$3604$,
candide wrote:
- les constantes (dommage que C n'ait pas emprunte a C++ en la matiere)


et les enums ?


A moitie pourries pour plein de raisons, entre autres parce qu'on ne sait pas
exactement a quel type elles correspondent. Pas trop utilisables pour des
constantes numeriques typees.


Avatar
espie
In article <47e97e4b$0$11987$,
candide wrote:
Je comprends pas : si les préprocesseurs sont conformes à la norme, il
ne devrait pas y avoir de différence.


Une proportion importante des preprocesseurs n'est pas conforme a la
norme, pour des raisons variees.

Avatar
candide
In article <47e98121$0$3604$,
candide wrote:
- les constantes (dommage que C n'ait pas emprunte a C++ en la matiere)
et les enums ?



A moitie pourries pour plein de raisons, entre autres parce qu'on ne sait pas
exactement a quel type elles correspondent.


Quoi ? les enum pourries ? le préprocesseur pas loin de l'être ? t'as un
autre langage à me conseiller ? C++ est une mauvaise réponse : la vie
est courte ;)



1 2 3 4 5