Определить, поддерживает ли компилятор C вложенные комментарии
Такая вот классическая задача, которая которую я когда-то увидел в книжке «C Traps and Pitfalls». Для современных компиляторов ответ однозначен: вложенные комментарии вида /*…/*…*/…*/ не поддерживаются, см. п. 6.4.9-1 стандарта C99. Но во времена динозавров, говорят, некоторые компиляторы все-таки поддерживали. Собственно, задача формулируется так:
Написать программу на C, компилирующуюся без ошибок (warning-и не в счет), которая при запуске выводит «YES», если вложенные комментарии поддерживаются, и «NO», если это не так.
Механизм, вокруг которого должна строиться такая программа, очевиден — вложенные комментарии:
/* всегда в комментарии /* всегда в комментарии */ какой-то-код */
Если вложенность поддерживается, какой-то-код — всего лишь часть комментария. Но если вложенности нет, то комментарий кончается на первом же «*/», а какой-то-код будет выполнен. Проблема только в одном: что делать с остающимися в хвосте «*/»? Наверное, нужно где-то раньше поместить парные «/*», но тогда код станет некорректным в случае, если вложенность поддерживается. Нетрудно видеть, что написание комментариев с еще большей глубиной вложенности лишь усугубляет проблему.
(Здесь самое время прекратить читать и попытаться придумать решение самостоятельно.)
Ключ к решению — различная лексическая трактовка символов в зависимости от контекста. Я придумал три способа:
- Использовать двойные кавычки. Символы между парными кавычками становятся частью строкового литерала. Конечно, придется использовать два набора вложенных комментариев.
- Использовать символ «*», в зависимости от ситуации, как оператор разыменования или оператор умножения. Опять нужно два набора комментариев.
- Поглотить «*/», сделав эту строку содержимым символа препроцессора. Здесь нужно также вспомнить, что комментарии обрабатываются препроцессором до обработки макродиректив.
Код выглядит немного странно, но разобраться несложно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // file: nestcomm.c? #include <stdio.h> void method1(){ printf("Method 1: "); printf("%s\n", /*/**/" NO\0*/"/*YES\0"/**/"*/"+2); } void method2(){ int a = 1; printf("Method 2: "); printf("%s\n", /*/**/2*/*(&a)+/**/2==4?"NO":"YES"); } void method3(){ printf("Method 3: "); /*/**/#define A */ printf("%s\n", #ifdef A "NO" #else "YES" #endif ); } int main(){ method1(); method2(); method3(); return 0; } |
Компилятор с поддержкой вложенных комментариев уже не найдешь, но можно его сымитировать, написав на flex небольшой препроцессор:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // file: snc.lex
%option noyywrap
%{
int comm = 0;
%}
%x STRING COMMENT
%%
<INITIAL>{
\" { BEGIN(STRING); ECHO; }
"/*" { comm++; BEGIN(COMMENT); }
. { ECHO; }
}
<COMMENT>{
"/*" { comm++; }
. { }
"*/" { comm--; if (!comm) BEGIN(INITIAL); }
}
<STRING>{
\" { BEGIN(INITIAL); ECHO; }
. { ECHO; }
}
%%
int main()
{
yylex();
return 0;
} |
Ну, и чтобы два раза не вставать, Makefile для автоматического прогона:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | .PHONY: all all: nest_yes nest_no @echo "With nested comments support:" ./nest_yes @echo "Without nested comments support:" ./nest_no nest_yes: nestcomm.c snc ./snc < $< > tmp.c cc -o $@ tmp.c rm -f tmp.c snc: snc.lex flex -o $(subst .lex,.c,$<) $< cc -o $@ $(subst .lex,.c,$<) nest_no: nestcomm.c cc -o $@ $< .PHONY: clean clean: rm -f snc.c snc nest_yes nest_no |
Многие считают подобные упражнения напрасной тратой времени, но на самом деле таким образом можно узнать удивительно много нового о тонкостях языка.


(2 голосов, средний: 4,50 из 5)