Препроцессор C сначала удаляет комментарии или раскрывает макросы?

Рассмотрим эту (ужасную, ужасную, плохую, очень плохую) структуру кода:

#define foo(x) // commented out debugging code

// Misformatted to not obscure the point
if (a)
foo(a);
bar(a);

Я видел, как препроцессоры двух компиляторов генерируют разные результаты в этом коде:

if (a)
bar(a);

и

if (a)
;
bar(a);

Очевидно, это плохо для переносимой базы кода.

Мой вопрос: что препроцессор должен с этим делать? Сначала удалите комментарии или сначала разверните макросы?


person Phil Miller    schedule 02.10.2009    source источник
comment
Хороший вопрос - заставил меня поработать над отслеживанием реальной информации :)   -  person Reed Copsey    schedule 02.10.2009
comment
К вашему сведению, используйте #define foo (x) ##, чтобы сделать более безопасный пустой макрос .... (или это ###?: /)   -  person Pod    schedule 02.10.2009
comment
Между прочим - какой компилятор вы используете, который ведет себя в вашем первом примере? Я почти уверен, что это сломает много кода - даже если было бы разумно использовать только / * * / комментарии в #define's, у меня сложилось впечатление, что я видел очень много использованных комментариев '//'.   -  person Michael Burr    schedule 03.10.2009
comment
Может быть, препроцессор не понимает // комментарии, а компилятор понимает? Помните, что изначально C должен был понимать только /* */ комментарии, а // был расширением C ++. Я думаю, что C подобрал // только с C99. (Правильно ли я ввел здесь свою историю?). Фактически, какой бы компилятор вы ни использовали, мне любопытно посмотреть, как он обрабатывает /*   -  person Aaron McDaid    schedule 19.02.2014


Ответы (6)


К сожалению, исходная Спецификация ANSI C специально исключает любые функции препроцессора в разделе 4 (" Эта спецификация описывает только язык C. Она не предусматривает ни библиотеки, ни препроцессора. ").

Однако спецификация C99 обрабатывает эту явную информацию. Комментарии заменяются одним пробелом на «фазе перевода», которая происходит до синтаксического анализа директивы Preprocessing. (Подробности см. В разделе 6.10).

VC ++ и GNU C Compiler оба следуют этой парадигме - другие компиляторы могут быть несовместимы, если они старше , но если он совместим с C99, будьте в безопасности.

person Reed Copsey    schedule 02.10.2009
comment
извините, но то, на что вы ссылаетесь, не соответствует спецификации ANSI C; фактическая спецификация описывает этапы перевода в разделе 2.1.1.2; Не так давно я опубликовал обзор этих фаз: stackoverflow.com/questions/1476892/ - person Christoph; 02.10.2009
comment
Ага - не уверен. Я всегда использую (в основном) компиляторы, совместимые с C99. Похоже, что OP использует компилятор C99, поскольку // as комментариев не существовало в C89. - person Reed Copsey; 02.10.2009
comment
Многие компиляторы C, с которыми я столкнулся, поддерживают комментарии C ++ / C99 '//', даже если они не поддерживают ничего другого из C99. - person Michael Burr; 02.10.2009
comment
@Novelocrat: Как я уже сказал, если вы используете C99, вы в безопасности. Однако технически существует несколько полностью совместимых компиляторов C99 (например, MS и GNU не на 100% совместимы). - person Reed Copsey; 03.10.2009

Как описано в этой копии- n-вставленное описание этапов перевода в стандарте C99, удаление комментариев (они заменяются одним пробелом) происходит на этапе перевода 3, в то время как директивы предварительной обработки обрабатываются, а макросы расширяются на этапе 4.

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

Опять же, в стандарте C ++ эти две фазы выполняются в одном и том же порядке.

Что касается того, как следует обрабатывать комментарии '//', стандарт C99 говорит следующее (6.4.9 / 2):

За исключением символьной константы, строкового литерала или комментария, символы // вводят комментарий, который включает в себя все многобайтовые символы до следующего символа новой строки, но не включая его.

И стандарт C ++ говорит (2.7):

Символы // начинают комментарий, который заканчивается следующим символом новой строки.

Итак, ваш первый пример явно является ошибкой со стороны этого переводчика - символ «;» после foo(a) должен быть сохранен при раскрытии макроса foo() - символы комментария не должны быть частью «содержимого» макроса the foo().

Но поскольку вы столкнулись с ошибкой переводчика, вы можете изменить определение макроса на:

#define foo(x) /* junk */

чтобы обойти ошибку.

Однако (и я здесь отклоняюсь от темы ...), поскольку сращивание строк (обратная косая черта непосредственно перед новой строкой) происходит до обработки комментариев, вы можете столкнуться с чем-то вроде этого неприятного кода:

#define evil( x) printf( "hello "); // hi there, \
                 printf( "%s\n", x); // you!



int main( int argc, char** argv)
{
    evil( "bastard");

    return 0;
}

Что может удивить того, кто это написал.

Или, что еще лучше, попробуйте следующее, написанное кем-то (конечно, не мной!), Кому нравятся комментарии в виде прямоугольников:

int main( int argc, char** argv)
{
                            //----------------/
    printf( "hello ");      // Hey, what the??/
    printf( "%s\n", "you"); // heck??         /
                            //----------------/
    return 0;
}

В зависимости от того, обрабатывает ли ваш компилятор по умолчанию триграфы или нет (компиляторы должны , но поскольку триграфы удивляют почти всех, кто сталкивается с ними, некоторые компиляторы решают отключить их по умолчанию), вы можете получить или не получить желаемое поведение - независимо от его поведения, конечно.

person Michael Burr    schedule 02.10.2009
comment
Или переместите комментарий со строки #define. - person jmucchiello; 02.10.2009
comment
Суть вопроса в том, что junk - это фактический код, который был закомментирован, когда не использовался для отладки. - person Phil Miller; 03.10.2009
comment
Этот ответ довольно хорош и содержит жемчужину правды, которая мне действительно помогает - используйте комментарии в стиле /* */ в макросах, и я в безопасности. - person Phil Miller; 03.10.2009

Согласно MSDN, комментарии заменяются одним пространство на этапе токенизации, который происходит перед этапом предварительной обработки, на котором раскрываются макросы.

person Jim Lewis    schedule 02.10.2009

Никогда не помещайте // комментарии в свои макросы. Если вы должны добавить комментарии, используйте / * * /. Кроме того, у вас есть ошибка в макросе:

#define foo(x) do { } while(0) /* junk */

Таким образом, foo всегда безопасно использовать. Например:

if (some condition)
    foo(x);

никогда не выдаст ошибку компилятора, независимо от того, определено ли foo для некоторого выражения.

person Vitali    schedule 02.10.2009
comment
junk - это код, который иногда является телом макроса. Мои предшественники не были так осторожны. - person Phil Miller; 03.10.2009
comment
Не могли бы вы пояснить, что вы имеете в виду? Трудно дать совет без конкретного примера. - person Vitali; 05.10.2009

#ifdef _TEST_
#define _cerr cerr
#else
#define _cerr / ## / cerr
#endif
  • будет работать на некоторых компиляторах (VC ++). Когда _TEST_ не определен,

    _cerr ...

    будет заменен строкой комментария

    // cerr ...

person Mike Peeler    schedule 06.01.2011

Мне кажется, я помню, что соблюдение требований требует трех шагов:

  1. strip
  2. expand macros
  3. strip again

Причина этого связана с тем, что компилятор может напрямую принимать файлы .i.

person Joshua    schedule 02.10.2009
comment
Отличный момент - это может стать сложным для многоступенчатой ​​предварительной обработки (чего лучше всего избежать полностью с помощью 'include guards', но есть некоторые интересные преимущества, особенно в разрешении зависимостей). - person John P; 05.07.2017