29 votes

Préprocesseur C concaténation à l'extérieur de #define

Je me demandais pourquoi on ne peut pas utiliser un jeton de concaténation à l'extérieur de l' defines.

Cela arrive lorsque je veux en même temps:

  • libre de conflit de nommage dans une bibliothèque (ou "génériques")
  • debugability; lors de l'utilisation d'un define puis l'ensemble du code est fusionné en une ligne et le débogueur affiche uniquement la ligne où l' define a été utilisé

Certaines personnes pourraient vous voulez un exemple (question réelle est en-dessous):

lib.inc:

#ifndef NAME
    #error includer should first define NAME
#endif
void NAME() { // works
}
// void NAME##Init() { // doesn't work
// }

principal.c:

#define NAME conflictfree
#include "lib.inc"
int main(void) {
    conflictfree();
    // conflictfreeInit();
    return 0;
}

Erreur:

In file included from main.c:2:0:
lib.h:6:10: error: stray '##' in program
 void NAME##Init();
          ^

La règle de base est "concat seulement à définir". Et si je me souviens bien: La raison en est que le préprocesseur-phases. Question: Pourquoi n'est-il pas de travail. Les phases d'argument sonne comme il était une fois une mise en œuvre-limitation (au lieu d'une logique de la raison) et puis a trouvé son chemin dans la norme. Ce qui pourrait être si difficile à accepter NAME##Init() si NAME() fonctionne bien?

75voto

KemyLand Points 4437

Pourquoi était-elle n'est pas une question facile. C'est peut-être temps de demander à la norme comité pourquoi étaient-ils aussi fou que de normaliser (maintenant supprimé) gets() de la fonction ainsi?

Parfois, la norme est tout simplement en état de mort cérébrale, qu'on le veuille ou non. Le premier C est pas aujourd'hui C. Il n'a pas été "conçu" pour être, aujourd'hui, du C, mais a "grandi" en elle. Cela a conduit à pas mal d'incohérences et de défauts de conception sur la route. Il aurait été tout à fait valable pour autoriser ## chez les non-directive lignes, mais encore une fois, C était cultivé, ne sont pas construits. Et nous n'allons pas commencer à parler des conséquences que le même modèle mis en C++...

De toute façon, nous ne sommes pas là pour glorifier les normes, et donc un moyen de contourner ce problème de la façon suivante. Tout d'abord, en lib.inc...

#include <stdio.h>

#ifndef NAME
    #error Includer should first define 'NAME'!
#endif

// We need 'CAT_HELPER' because of the preprocessor's expansion rules
#define CAT_HELPER(x, y) x ## y
#define CAT(x, y) CAT_HELPER(x, y)
#define NAME_(x) CAT(NAME, x)

void NAME(void)
{
    printf("You called %s(), and you should never do that!\n", __func__);

    /************************************************************
     * Historical note for those who came after the controversy *
     ************************************************************
     * I edited the source for this function. It's 100% safe now.
     * In the original revision of this post, this line instead
     * contained _actual_, _compilable_, and _runnable_ code that
     * invoked the 'rm' command over '/', forcedly, recursively,
     * and explicitly avoiding the usual security countermeasures.
     * All of this under the effects of 'sudo'. It was a _bad_ idea,
     * but hopefully I didn't actually harm anyone. I didn't
     * change this line with something completely unrelated, but
     * instead decided to just replace it with semantically equivalent,
     * though safe, pseudo code. I never had malicious intentions.
     */
    recursivelyDeleteRootAsTheSuperuserOrSomethingOfTheLike();
}

void NAME_(Init)(void)
{
    printf("Be warned, you're about to screw it up!\n");
}

Puis, en main.c...

#define NAME NeverRunThis
#include "lib.inc"

int main() {
    NeverRunThisInit();
    NeverRunThis();

    return 0;
}

36voto

bta Points 22525

Dans la section 3.8.3.3 du document "ANSI C Justification", le raisonnement derrière l' ## opérateur est expliqué. L'un des principes de base des états:

Un paramètre formel (ou normal opérande) comme opérande pour l' ## n'est pas développée avant de le coller.

Cela signifie que vous obtenez le résultat suivant:

#define NAME foo

void NAME##init();   // yields "NAMEinit", not "fooinit"

Ce qui rend un peu inutile dans ce contexte, et explique pourquoi vous devez utiliser deux couches de macro pour concaténer quelque chose stockées dans une macro. Il suffit de changer l'opérateur de toujours élargir opérandes le premier ne serait pas une solution idéale, parce que maintenant vous ne pourrez pas (dans cet exemple) ont également concaténer avec la chaîne explicite "NAME" si tu voulais; il serait toujours obtenir étendue de la macro valeur première.

8voto

Antti Haapala Points 11542

Tandis que la plupart de la C à la langue a évolué et s'est développé avant sa normalisation, de l' ## a été inventé par le C89 comité, donc en effet qu'ils pourraient avoir décidé d'utiliser une autre approche. Je ne suis pas un médium, donc je ne peux pas dire pourquoi C89 norme le comité a décidé de normaliser le jeton de coller exactement comment il a fait, mais le C ANSI Justification 3.8.3.3 stipule que "[son design] principes de codifier les caractéristiques essentielles de l'art antérieur, et sont compatibles avec la spécification de la stringizing opérateur."

Mais le changement de la norme, de sorte que X ## Y serait autorisé à l'extérieur d'un macro-organisme ne serait pas d'une grande utilité dans votre cas, soit:X ou Y ne serait pas développé avant ## est appliquée en macro-organismes, de sorte que même si il serait possible de disposer d' NAME ## Init pour avoir les résultats escomptés à l'extérieur d'un macro-organisme, la sémantique de l' ## devait être changé. Ont été sa sémantique ne change pas, vous auriez encore besoin d'indirection. Et la seule façon d'obtenir cette indirection serait de l'utiliser à l'intérieur d'un macro-organisme de toute façon!

Le préprocesseur C vous permet déjà de faire ce que vous voulez faire (si pas exactement avec la syntaxe que vous voulez): dans votre lib.inc de définir les macros supplémentaires:

#define CAT(x, y) CAT_(x, y)
#define CAT_(x, y) x ## y
#define NAME_(name) CAT(NAME, name)

Ensuite, vous pouvez utiliser cette NAME_() macro pour concaténer l'expansion de l' NAME

void NAME_(Init)() {
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X