Сначала препроцессор 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);
Очевидно, что это плохо для переносимой базы кода.
Мой вопрос: что препроцессор должен делать с этим? Сначала комментарии Elide, или сначала раскройте макросы?
Ответы
Ответ 1
К сожалению, оригинальная спецификация ANSI C специально исключает любые функции препроцессора в разделе 4 ( "В этой спецификации описывается только язык C. Это не делает либо для библиотеки, либо для препроцессора." ).
спецификация C99 обрабатывает эту экспликацию. Комментарии заменяются одним пространством в "фазе перевода", которое происходит до синтаксического анализа директивы Preprocessing. (Подробнее см. Раздел 6.10).
VС++ и компилятор GNU C и следуют этому Парадигма - другие компиляторы могут быть несовместимы, если они старше, но если он совместим с C99, вы должны быть в безопасности.
Ответ 2
Как описано в этом скопированном n-образном расширении фаз перевода в стандарте C99, удаление комментариев (они заменяются одним пробелом) происходит в переводе фаза 3, в то время как обрабатываются директивы предварительной обработки, а макросы расширяются в фазе 4.
В стандарте C90 (который у меня есть только в печатном виде, поэтому нет копии-n-paste) эти две фазы происходят в одном порядке, хотя описание фаз перевода несколько отличается в некоторых деталях от стандарта C99 - тот факт, что комментарии удаляются и заменяются одним символом пробела перед обработкой препроцессора, а макросы расширены, не отличается.
Опять же, стандарт С++ имеет эти 2 фазы в одном порядке.
В отношении того, как следует обрабатывать комментарии "//
", стандарт C99 говорит об этом (6.4.9/2):
За исключением внутри символьной константы, строкового литерала или комментария символов // ввести комментарий, который включает в себя все многобайтовые символы до, но не включая следующий символ новой строки.
И стандарт С++ говорит (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;
}
Что может удивить любого, кто его написал.
Или даже лучше, попробуйте следующее, написанное кем-то (конечно, не я!), которому нравятся комментарии в стиле box:
int main( int argc, char** argv)
{
//----------------/
printf( "hello "); // Hey, what the??/
printf( "%s\n", "you"); // heck?? /
//----------------/
return 0;
}
В зависимости от того, будет ли ваш компилятор по умолчанию обрабатывать триграфы или нет (компиляторы должны быть, но поскольку триграфы удивляют почти всех, кто сталкивается с ними, некоторые компиляторы решают их по умолчанию), вы можете или не можете получить поведение, которое вы хотите - независимо от поведения, которое, конечно же.
Ответ 3
Согласно MSDN, комментарии заменяются одним пробелом на фазе токенизации,
который происходит до фазы предварительной обработки, где макросы расширяются.
Ответ 4
Никогда не помещайте//комментарии в свои макросы. Если вы должны поставить комментарии, используйте /* */. Кроме того, у вас есть ошибка в вашем макросе:
#define foo(x) do { } while(0) /* junk */
Таким образом, foo всегда безопасен в использовании. Например:
if (some condition)
foo(x);
никогда не будет вызывать ошибку компилятора независимо от того, определено ли foo для некоторого выражения.
Ответ 5
#ifdef _TEST_
#define _cerr cerr
#else
#define _cerr / ## / cerr
#endif
-
будет работать с некоторыми компиляторами (VС++). Если _TEST_
не определено,
_cerr...
будет заменен на строку комментария
//cerr...
Ответ 6
Кажется, я помню, что соблюдение требует трех шагов:
- полосы
- expand макросы
- strip снова
Причина этого связана с тем, что компилятор может напрямую принимать файлы .i.