Препроцессор C, рекурсивные макросы

Почему 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

Ответы

Ответ 1

Фактически, это зависит от вашей интерпретации языкового стандарта. Например, в 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). В спецификации указано, что окончательный 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)

Если вместо M_0 было заменено на CAT(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 он не может дать бесконечную рекурсию.

Ответ 2

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

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)

Ответ 3

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