Зачем мне нужен двойной слой косвенности для макросов? - PullRequest
24 голосов
/ 22 ноября 2011

По адресу: C ++ FAQ - Разные технические проблемы - [39.6] Что делать с макросами, в которые нужно вставить два токена вместе?

Может ли кто-нибудь объяснить мне почему?Все, что я прочитал, это поверь мне , но я просто не могу просто доверять чему-то, потому что кто-то так сказал.

Я попробовал подход, и я не могу найти никаких ошибок:

#define mymacro(a) int a ## __LINE__
mymacro(prefix) = 5;
mymacro(__LINE__) = 5;
int test = prefix__LINE__*__LINE____LINE__; // fine

Так что почему мне нужно сделать это вместо этого (цитата с веб-страницы):

Однако вам нужен двойной слой косвенности, когда выиспользуйте ##.По сути, вам нужно создать специальный макрос для «вставки токена», такой как:

 #define NAME2(a,b)         NAME2_HIDDEN(a,b)
 #define NAME2_HIDDEN(a,b)  a ## b 

Поверьте мне - вам действительно нужно это сделать!(И, пожалуйста, никто не пишите мне, говоря, что иногда это работает без второго уровня косвенности. Попробуйте объединить символ с помощью __LINE__ и посмотрите, что произойдет потом.)

Редактировать: Может кто-тотакже объясните, почему он использует NAME2_HIDDEN до того, как это будет объявлено ниже?Кажется более логичным определить макрос NAME2_HIDDEN, прежде чем я его использую.Это какая-то хитрость здесь?

Ответы [ 5 ]

30 голосов
/ 22 ноября 2011

Соответствующая часть спецификации C:

6.10.3.1 Подстановка аргумента

После того, как аргументы для вызова функционально-подобного макроса были определены, замена аргумента имеет место. Параметр в списке замены, если не предшествует # или ## токеном предварительной обработки или сопровождаемым ## токеном предварительной обработки (см. ниже), является заменяется соответствующим аргументом после того, как все содержащиеся в нем макросы были расширен. Перед заменой токены предварительной обработки каждого аргумента полностью замененные макросы, как будто они образуют остальную часть файла предварительной обработки; нет другого токены предварительной обработки доступны.

Ключевой частью, которая определяет, хотите ли вы двойное косвенное обращение, является второе предложение и исключение в нем - если параметр участвует в операции # или ## (например, параметры в * 1011) * и NAME2_HIDDEN), то любые другие макросы в аргументе НЕ раскрываются до выполнения # или ##. Если, с другой стороны, в теле макроса нет # или ## НЕМЕДЛЕННО (как с NAME2), тогда другие макросы в параметрах расширяются.

Таким образом, все сводится к тому, что вы хотите - иногда вы хотите, чтобы все макросы были развернуты ПЕРВЫМ, а затем выполняете # или ## (в этом случае вы хотите использовать двухслойное перенаправление), а иногда вам НЕ НУЖНО макросы сначала раскрываются (в этом случае вы НЕ МОЖЕТЕ иметь двухслойные макросы, вам нужно сделать это напрямую)

4 голосов
/ 22 ноября 2011

__LINE__ - это специальный макрос, который должен преобразовываться в текущий номер строки. Однако, когда вы вставляете токен непосредственно с помощью __LINE__, у него не будет возможности разрешить его, поэтому вы получите токен prefix__LINE__ вместо, скажем, prefix23, как вы, вероятно, ожидаете, если вы бы написали этот код в дикой природе.

3 голосов
/ 23 ноября 2011

У Криса Додда есть отличное объяснение первой части вашего вопроса. Что касается второй части, касающейся последовательности определения, короткая версия состоит в том, что директивы #define сами по себе не оцениваются вообще; они оцениваются и раскрываются только тогда, когда символ находится в другом месте файла. Например:

#define A a  //adds A->a to the symbol table
#define B b  //adds B->b to the symbol table

int A;

#undef A     //removes A->a from the symbol table
#define A B  //adds A->B to the symbol table

int A;

Первый int A; становится int a;, потому что именно так A определяется в этой точке файла. Второй int A; становится int b; после двух расширений. Сначала он расширяется до int B;, поскольку A определяется как B в этой точке файла. Затем препроцессор распознает, что B является макросом, когда проверяет таблицу символов. B затем расширяется до b.

Единственное, что имеет значение, это определение символа в точке расширения, независимо от того, где это определение.

2 голосов
/ 09 февраля 2018

Наиболее нетехнический ответ, который я собрал из всех ссылок здесь и ссылки ссылок;), заключается в том, что однонаправленная косвенность macro(x) #x приводит к строковому преобразованию имени введенного макроса, но при использовании двойных слоев он приводит к строковому преобразованию.значение введенного макроса.

#define valueOfPi 3
#define macroHlp(x) #x
#define macro(x) macroHlp(x)  
#define myVarOneLayer "Apprx. value of pi = " macroHlp(valueOfPi)
#define myVarTwoLayers "Apprx. value of pi = " macro(valueOfPi)

printf(myVarOneLayer); // out: Apprx. value of pi = valueOfPi 
printf(myVarOTwoLayers); // out: Apprx. value of pi = 3

То, что происходит при printf(myVarOneLayer)

printf(myVarOneLayer), расширяется до printf("Apprx. value of pi = " macroHlp(valueOfPi))

macroHlp(valueOfPi), пытаясь структурировать ввод,Сам вход не оценивается.Это единственная цель в жизни, чтобы взять ввод и зачеркнуть.Таким образом, он расширяется до "valueOfPi"

Итак, то, что происходит в printf(myVarTwoLayers)

printf(myVarTwoLayers), расширяется до printf("Apprx. value of pi = " macro(valueOfPi)

macro(valueOfPi) не имеет операции строкового преобразования,то есть в его расширении нет #x, но есть x, поэтому он должен оценить x и ввести значение в macroHlp для строкового преобразования.Он расширяется до macroHlp(3), что, в свою очередь, приведет к строковому значению числа 3, так как он использует #x

1 голос
/ 15 октября 2012

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...