web-dev-qa-db-fra.com

Concaténation du préprocesseur C en dehors de #define

Je me demandais pourquoi nous ne pouvons pas utiliser la concaténation de jetons en dehors de defines.

Cela se produit lorsque je les souhaite en même temps:

  • nommage sans conflit dans une bibliothèque (ou pour les "génériques")
  • débogage; lors de l'utilisation d'un define pour cela, tout le code est fusionné en une ligne et le débogueur n'affiche que la ligne où le define a été utilisé

Certaines personnes pourraient vouloir un exemple ( la 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 d'or est "concaténer uniquement en définir". Et si je me souviens bien: La raison est à cause des phases de préprocesseur. Question: Pourquoi ça ne marche pas. L'argument phases semble être une limitation d'implémentation (au lieu d'une raison logique) et a ensuite trouvé sa place dans la norme. Qu'est-ce qui pourrait être si difficile à accepter NAME##Init() si NAME() fonctionne bien?

29
Bernd Elkemann

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

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

Cela signifie que vous obtiendrez les éléments suivants:

#define NAME foo

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

Cela le rend plutôt inutile dans ce contexte et explique pourquoi vous devez utiliser deux couches de macro pour concaténer quelque chose stocké dans une macro. Changer simplement l'opérateur pour toujours développer d'abord les opérandes ne serait pas une solution idéale, car maintenant vous ne pourriez pas (dans cet exemple) concaténer aussi avec la chaîne explicite "NAME" si vous le vouliez; il serait toujours étendu à la valeur de la macro en premier.

37
bta

Pourquoi était ce n'est pas une question facile. Peut-être qu'il est temps de demander au comité standard pourquoi étaient-ils aussi fous que de standardiser (la désormais supprimée) gets() fonction aussi?

Parfois, la norme est tout simplement mortelle, que nous le voulions ou non. Le premier C n'était pas le C d'aujourd'hui. Il n'était pas "conçu" pour être le C d'aujourd'hui, mais "y a grandi". Cela a entraîné de nombreuses incohérences et des défauts de conception sur la route. Il aurait été parfaitement valable de permettre à ## dans des lignes non directives, mais encore une fois, C a été développé, pas construit. Et ne commençons pas à parler des conséquences que ce même modèle a apportées au C++ ...

Quoi qu'il en soit, nous ne sommes pas ici pour glorifier les normes, donc une façon de contourner cela suit. Tout d'abord, dans 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 dans main.c...

#define NAME NeverRunThis
#include "lib.inc"

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

    return 0;
}
80
3442

Alors qu'une grande partie du langage C avait évolué et développé avant sa standardisation, le ## A été inventé par le comité C89, donc en effet, ils auraient pu décider d'utiliser une autre approche également. Je ne suis pas un médium, donc je ne peux pas dire pourquoi le comité standard C89 a décidé de standardiser le jeton en collant exactement comment il l'a fait, mais la justification ANSI C 3.8.3.3 déclare que les principes de "[sa conception] codifient les caractéristiques essentielles de l'art antérieur et sont conformes à la spécification de l'opérateur de stringing."

Mais changer la norme pour que X ## Y Soit autorisé en dehors d'un corps de macro ne serait pas très utile dans votre cas: X ou Y ne serait pas développé avant ## Est également appliqué dans les corps de macro, donc même s'il était possible d'avoir NAME ## Init Pour avoir les résultats souhaités en dehors d'un corps de macro, la sémantique de ## Devrait être modifié. Si sa sémantique n'était pas modifiée, vous auriez toujours besoin d'une indirection. Et la seule façon d'obtenir cette indirection serait de l'utiliser de toute façon dans un corps de macro!

Le le préprocesseur C vous permet déjà de faire ce que vous voulez faire (sinon exactement avec la syntaxe que vous souhaitez): dans votre lib.inc Définissez les macros supplémentaires suivantes:

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

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

void NAME_(Init)() {
}
9
Antti Haapala