C конкатенация препроцессора вне #define

Мне было интересно, почему мы не можем использовать конкатенацию маркера вне define s.

Это происходит, когда я хочу их в одно и то же время:

  • конфликтующее именование в библиотеке (или для "generics" )
  • debugability; при использовании define для этого весь код объединяется в строку, и отладчик будет показывать только строку, в которой был использован define

Некоторым людям может понадобиться пример (актуальный вопрос ниже):

lib.inc:

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

main.c:

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

Ошибка:

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

Эмпирическое правило "concat only in define". И если я правильно помню: причина - из-за фаз препроцессора. Вопрос: Почему он не работает. Аргумент фаз звучит так, как будто это было ограничение реализации (а не логическая причина), а затем нашло свое отражение в стандарте. Что может быть так сложно в принятии NAME##Init(), если NAME() отлично работает?

Ответ 1

В разделе 3.8.3.3 документа "Обоснование ANSI C" объясняется аргументация оператора ##. Один из основных принципов гласит:

Формальный параметр (или нормальный операнд) в качестве операнда для ## не расширяется до вставки.

Это означает, что вы получите следующее:

#define NAME foo

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

Это делает его бесполезным в этом контексте и объясняет, почему вы должны использовать два уровня макроса для конкатенации чего-либо, хранящегося в макросе. Простое изменение оператора, чтобы всегда разворачивать операнды, не было бы идеальным решением, потому что теперь вы не сможете (в этом примере) также объединиться с явной строкой "NAME", если хотите; он всегда будет расширяться до значения макроса.

Ответ 2

Почему это был непростой вопрос. Может быть, пришло время спросить у стандартного комитета, почему они были такими же сумасшедшими, как и стандартизация (теперь удаленная) функция gets()?

Иногда, стандарт просто мозг мертв, хотим мы этого или нет. Первый C не был сегодня C. Он не был "разработан" сегодня C, но "вырос" в нем. Это привело к немало несогласованности и недостаткам дизайна на дороге. Было бы совершенно справедливо разрешить ## в непрямых строках, но опять же, C был выращен, а не построен. И пусть не начнет говорить о последствиях, которые та же самая модель привнесла в С++...

В любом случае, мы не здесь, чтобы прославлять стандарты, поэтому один из способов обойти это следует. Прежде всего, в lib.inc...

#include <stdio.h>

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

// We need 'CAT_HELPER' because of the preprocessor 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 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");
}

Затем в main.c...

#define NAME NeverRunThis
#include "lib.inc"

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

    return 0;
}

Ответ 3

В то время как большая часть языка C развивалась и развивалась до его стандартизации, ## был изобретен комитетом C89, так что действительно они могли бы использовать и другой подход. Я не экстрасенс, поэтому я не могу сказать, что почему стандартный комитет C89 решил стандартизировать токен, вставляя именно то, что он сделал, но в ANSI C Rationale 3.8.3.3 говорится, что "принципы [его дизайна] черты предшествующего уровня техники, и соответствуют спецификации оператора стробирования."

Но изменение стандарта так, чтобы X ## Y было разрешено за пределами тела макроса, было бы не очень полезно в вашем случае: X или Y не были бы расширены до того, как ## будет применен в макросе тела, так что даже если бы было возможно иметь NAME ## Init для предполагаемых результатов вне тела макроса, необходимо было бы изменить семантику ##. Если бы семантика не изменилась, вам все равно понадобилось бы косвенное. И единственный способ получить эту косвенность - это использовать его в пределах тела макроса!

Препроцессор C уже позволяет вам делать то, что вы хотите сделать (если не в точности с синтаксисом, который вам нужен): в lib.inc define следующие дополнительные макросы:

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

Затем вы можете использовать этот макрос NAME_(), чтобы объединить расширение NAME

void NAME_(Init)() {
}