Convertir un #define en chaîne de caractères
Cet article provient de mon ancien site Internet.
À quoi ça sert ?
Très bonne question !
À quoi ça peut bien servir de convertir un #define
en chaîne de
caractères ?
Et bien, pour l’afficher pardi !
J’en vois déjà certains venir en grognant : « Le debug à coup
de printf
c’est crade ! ». Bah non ! Enfin, ça dépend de comment
c’est mis en place : si c’est sous forme de logs, ça peut être très propre. Et
puis des fois, on n’a que les logs pour debugger…
Certains me diront que l’on peut utiliser directement printf
, et
ils n’ont pas tout à fait tort. Mais en fait ça devient vite chiant pour deux
raisons :
-
Si vous changez le type d’un de vos
#define
, vous êtes bon pour modifier toutes vos chaînes de format. Ok, certains compilateurs (comme gcc une fois bien réglé) peuvent vous aider à repérer ces endroits, mais le remplacement reste à votre charge ; -
Si vous voulez afficher une macro substituée (toujours à des fins
de debug, pour voir si une macro est bien développée comme prévu),
vous allez faire comment avec
printf
? Certes, gcc (et clang) propose l’option-E
pour voir le code après passage du préprocesseur (fort pratique), mais quid des autres compilateurs (on n’a pas toujours la possibilité de bosser avec de bons compilateurs) ?
Et comment on fait ça ?
Première tentative
Comme vous êtes des gens un minimum cultivé en C, vous avez tout de suite
pensé à l’opérateur #
qui permet de convertir un paramètre de
macro en chaîne de caractères.
Bien vu ! C’est comme cela qu’il faut procéder. Allez, faisons quelques tests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h> #define TO_STR(a) #a #define DOT '.' #define INTEGER 42 #define MY_PI 3.14 #define STRING "42" #define SQUARE(x) ((x)*(x)) int main(void) { puts(TO_STR(DOT)); puts(TO_STR(INTEGER)); puts(TO_STR(MY_PI)); puts(TO_STR(STRING)); puts(TO_STR(SQUARE(SQUARE(INTEGER)))); return 0; } |
Et maintenant, testons ce code.
Hum :-/, ce n’est pas vraiment le résultat attendu (on affiche les symboles à la place des valeurs). Mais alors, où est le problème ?
Petite explication
Et bien comme souvent, il suffit de lire la norme. Et la norme (ISO/IEC 9899:TC3, 6.10.3 Macro replacement, page 152) nous dit :
If a # preprocessing token, followed by an identifier, occurs lexically at the point at which a preprocessing directive could begin, the identifier is not subject to macro replacement.
Donc, si le symbole qui suit l’opérateur #
est en fait un
identifiant, il ne sera pas substitué. Cela explique la sortie produite par le
code précèdent.
Par exemple, pour INTEGER
, TO_STR(INTEGER)
est
remplacé par #INTEGER
lors de la substitution de la macro, puis
l’opérateur #
est appliqué (ici pas de substitution à cause de la
règle précedemment citée, donc INTEGER
n’est pas remplacé par 42)
et on obtient la chaîne de caractères INTEGER.
La solution
La solution est simple, si l’opérateur #
ne fait pas la
substitution il nous suffit de la faire nous‑même avant l’appel. Pour cela,
nous allons utiliser une macro intermédiaire.
Notre définition de TO_STR
devient donc :
1 2 | #define TO_STR_(a) #a
#define TO_STR(a) TO_STR_(a)
|
Et voilà, maintenant on obtient bien le résultat attendu.
Par exemple, pour INTEGER
, TO_STR(INTEGER)
est
remplacé par TO_STR_(42)
(la première macro fait la substitution)
puis la seconde macro est remplacée à son tour et l’on
obtient #42
. Une fois l’opérateur #
appliqué on
obtient bien la chaîne de caractères 42.