webentwicklung-frage-antwort-db.com.de

C Präprozessor-Verkettung außerhalb von #define

Ich habe mich gefragt , warum wir keine Token-Verkettung außerhalb von defines verwenden können.

Das kommt, wenn ich diese gleichzeitig will:

  • konfliktfreie Benennung in einer Bibliothek (oder für "Generics")
  • debugfähigkeit; Wenn Sie hierfür ein define verwenden, wird der gesamte Code in einer Zeile zusammengeführt und der Debugger zeigt nur die Zeile an, in der das define verwendet wurde

Einige Leute möchten vielleicht ein Beispiel ( die eigentliche Frage ist darunter ):

lib.inc:

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

haupt c:

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

Error:

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

Die Faustregel lautet "concat only in define". Und wenn ich mich recht erinnere: Der Grund liegt in den Preprozessor-Phasen. Frage: Warum funktioniert nicht. Das Phasen-Argument klingt so, als wäre es einmal eine Einschränkung der Implementierung gewesen (anstelle eines logischen Grundes) und hat dann seinen Weg in den Standard gefunden. Was könnte so schwierig sein, NAME##Init() zu akzeptieren, wenn NAME() gut funktioniert?

29
Bernd Elkemann

In Abschnitt 3.8.3.3 des Dokuments "ANSI C Rationale" wird die Begründung hinter dem ## Operator wird erklärt. Eines der Grundprinzipien lautet:

Ein Formalparameter (oder normaler Operand) als Operand für ## wird vor dem Einfügen nicht erweitert.

Dies bedeutet, dass Sie Folgendes erhalten würden:

#define NAME foo

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

Dies macht es in diesem Zusammenhang ziemlich nutzlos und erklärt, warum Sie zwei Makroebenen verwenden müssen, um etwas zu verketten, das in einem Makro gespeichert ist. Das einfache Ändern des Operators, um Operanden immer zuerst zu erweitern, wäre keine ideale Lösung, da Sie jetzt (in diesem Beispiel) nicht auch mit der expliziten Zeichenfolge "NAME" verketten können, wenn Sie dies möchten. Es wird immer zuerst auf den Makro-Wert erweitert.

37
bta

Warum war es keine einfache Frage. Vielleicht ist es an der Zeit, das Standardkomitee zu fragen, warum sie so verrückt waren, auch die (jetzt entfernte) gets() -Funktion zu standardisieren?

Manchmal ist der Standard einfach hirntot, ob wir es wollen oder nicht. Das erste C war nicht das heutige C. Es wurde nicht als das heutige C "entworfen", sondern ist darin "aufgewachsen". Dies hat zu einigen Inkonsistenzen und Designfehlern auf der Straße geführt. Es wäre völlig richtig gewesen, ## in nicht direktiven Zeilen, aber wieder wurde C vergrößert, nicht gebaut. Und lassen Sie uns nicht über die Konsequenzen sprechen, die dasselbe Modell für C++ mit sich brachte ...

Wie auch immer, wir sind nicht hier, um die Standards zu verherrlichen, daher folgt ein Weg, dies zu umgehen. Zunächst in 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");
}

Dann in main.c...

#define NAME NeverRunThis
#include "lib.inc"

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

    return 0;
}
80
3442

Während sich ein Großteil der C-Sprache vor ihrer Standardisierung entwickelt und weiterentwickelt hatte, wurde der ## Vom C89-Komitee erfunden , sodass sie sich in der Tat für einen anderen Ansatz hätten entscheiden können auch. Ich bin kein Hellseher und kann daher nicht sagen , warum das C89-Standardkomitee beschlossen hat, das Token-Einfügen genau so zu standardisieren, wie es getan wurde, aber die ANSI-C-Begründung 3.8.3.3 gibt an, dass "[seine Entwurfsprinzipien] die wesentlichen Merkmale des Standes der Technik kodieren und mit der Spezifikation des Stringing-Operators übereinstimmen."

Aber das Ändern des Standards, so dass X ## Y Außerhalb eines Makrotexts zulässig ist, ist in Ihrem Fall auch nicht hilfreich: X oder Y werden nicht vor ## Wird auch in Makrokörpern angewendet. Selbst wenn es möglich wäre, NAME ## Init Zu haben, um die beabsichtigten Ergebnisse außerhalb eines Makrokörpers zu erzielen, müsste die Semantik von ## Lauten geändert. Wäre die Semantik nicht geändert worden, bräuchten Sie immer noch eine Indirektion. Und die einzige Möglichkeit, diese Indirektion zu erhalten, wäre, sie in einem Makrokörper zu verwenden!

Der C-Präprozessor ermöglicht es Ihnen bereits, das zu tun, was Sie tun möchten (wenn nicht genau mit der gewünschten Syntax): Definieren Sie in Ihrem lib.inc Die folgenden zusätzlichen Makros:

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

Dann können Sie dieses Makro NAME_() verwenden, um die Erweiterung von NAME zu verketten.

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