Препроцессор C, рекурсивные макросы - PullRequest
14 голосов
/ 13 апреля 2011

Почему M (0) и N (0) имеют разные результаты?

#define CAT_I(a, b) a ## b
#define CAT(a, b) CAT_I(a, b)

#define M_0 CAT(x, y)
#define M_1 whatever_else
#define M(a) CAT(M_, a)
M(0);       //  expands to CAT(x, y)

#define N_0() CAT(x, y)
#define N_1() whatever_else
#define N(a) CAT(N_, a)()
N(0);       //  expands to xy

Ответы [ 3 ]

17 голосов
/ 13 апреля 2011

На самом деле, это зависит от вашей интерпретации стандарта языка. Например, в mcpp, реализации препроцессора, которая строго соответствует тексту языкового стандарта, вторая также выдает CAT(x, y); [дополнительные результаты были удалены из результата]:

C:\dev>mcpp -W0 stubby.cpp
#line 1 "C:/dev/stubby.cpp"
        CAT(x, y) ;
        CAT(x, y) ;
C:\dev>

Существует известное несоответствие в спецификации языка C ++ (такое же несоответствие присутствует в спецификации C, хотя я не знаю, где находится список дефектов для C). В спецификации говорится, что окончательное значение CAT(x, y) не должно заменяться макросами. Намерение, возможно, состояло в том, что это должно быть заменено макросом.

Цитировать связанный отчет о дефектах:

Еще в 1980-х годах несколько человек из WG14 поняли, что между словами «без замены» и попытками создать псевдокод существует небольшая разница.

Решение комитета состояло в том, что никакие реалистичные программы "в дикой природе" не рискнут проникнуть в эту область, и попытка уменьшить неопределенности не стоит риска изменения статуса соответствия реализаций или программ.


Итак, почему мы получаем другое поведение для M(0), чем для N(0) с наиболее распространенными реализациями препроцессора? При замене M второй вызов CAT полностью состоит из токенов, полученных в результате первого вызова CAT:

M(0) 
CAT(M_, 0)
CAT_I(M_, 0)
M_0
CAT(x, y)

Если вместо этого вместо CAT(M, 0) определено M_0, замена будет повторяться бесконечно. Спецификация препроцессора явно запрещает эту «строго рекурсивную» замену путем остановки замены макроса, поэтому CAT(x, y) не заменяется макросом.

Однако при замене N второй вызов CAT состоит из только частично токенов, полученных в результате первого вызова CAT:

N(0)
CAT(N_, 0)       ()
CAT_I(N_, 0)     ()
N_0              ()
CAT(x, y)
CAT_I(x, y)
xy

Здесь второй вызов CAT формируется частично из токенов, полученных в результате первого вызова CAT, и частично из других токенов, а именно () из списка замены N. Замена не является строго рекурсивной, и поэтому при замене второго вызова CAT она не может привести к бесконечной рекурсии.

4 голосов
/ 13 апреля 2011

Просто следуйте последовательности:

1.)

M(0); //  expands to CAT(x, y) TRUE 
CAT(M_, 0)
CAT_I(M_, 0)
M_0
CAT(x, y)

2.)

N(0); //  expands to xy TRUE
CAT(N_, 0)()
CAT_I(N_, 0)()
N_0()
CAT(x, y)
CAT_I(x, y)
xy

Вам нужно только рекурсивно заменить макросы.

Примечания к оператору препроцессора ##: Два аргумента можно «склеить», используя оператор препроцессора ##;это позволяет объединять два токена в предварительно обработанном коде.

В отличие от стандартного расширения макросов, традиционное расширение макросов не предусматривает предотвращения рекурсии.Если объектоподобный макрос появляется в тексте замены без кавычек, он будет заменен снова во время прохода повторного сканирования и т. Д. До бесконечности.GCC определяет, когда он расширяет рекурсивные макросы, выдает сообщение об ошибке и продолжает работу после вызова вызывающего макроса.( gcc online doc )

0 голосов
/ 13 апреля 2011

Возможно, что-то не удалось обнаружить, но ваш макрос имеет N(a) CAT(N_,a)(), тогда как M (a) определяется как CAT(M_, a) Обратите внимание на дополнительные скобки параметров, используемые ....

...