среда, февраля 01, 2006

Триграфы и альтернативные символы

The addition of trigraphs has made it easier to make programs hard to read. It was a dark and stormy night for C when the ANSI C committee added trigraphs to the standard.

Это фраза из старых правил конкурса Obfuscated C, конкурса на самый запутанный код на языке C.
Вообще триграфы были придуманы для терминалов, в которых некоторых символов не хватает. В итоге вместо #define можно написать ??=define. Триграфы подменяются на нужные символы в самом начале, поэтому эти записи эквивалентны. Вместо { можно написать ??< , вместо } использовать ??> .

ТриграфЧто заменяет
??= #
??( [
??/ \
??) ]
??’ ^
??< {
??! |
??> }
??- ~

Кроме триграфов есть еще альтернативные символы. Например вместо тильды ~ можно использовать compl, решетку # можно заменить на %: .
Альтернативные символы, которые не являются словами, известны как диграфы. Даже несмотря на то, что есть диграф, состоящий из четырех символов.

ДиграфЧто заменяет
<% {
%>}
<:[
:> ]
%: #
%:%: ##

С триграфами связан один неприятный момент: можно опечататься и получить весьма странное поведение программы. Классический пример опечатки, которая превращается в тригаф, приведен у Герба Саттера. GotW #86: Slight Typos? Graphic Language and Other Curiosities

// What will the next line do? Increment???????????/
++x;

Символы '?' и '/' находятся на одной кнопке и опечататься так несложно. Но последовательность ??/ - это триграф, который заменяется на '\', что означает конкатенацию строк. Код превращается в

// What will the next line do? Increment?????????++x;

В gcc неприятностей от триграфов меньше, потому что там надо указать опцию -trigraphs для обработки триграфов. Кстати, в мануале к одной из старых версий gcc об опции -trigraphs сказано "You don't want to know about this brain-damage". :-)
А вот в Visual С++ никаких опций для триграфов указывать не надо, хотя для альтернативных символов - надо...

Триграфы будут заменены везде в коде, например внутри текстовых строк. И вот такой код

printf( "??-I wonder!\n" );

Выдаст на печать

~I wonder!

Так как триграф ??- будет заменен на тильду. Для получения желаемого вывода придется использовать обратные слэши.

printf( "\?\?-I wonder!\n" );

Для подавляющего большинства программистов, пользы от триграфов - никакой, одни неприятности... Хотя, можно изобрести какой-нибудь хоррор-код, а потом приставать ко всем с глупым вопросом: "Почему данный С++ код корректен и что он делает?".

??<z=compl(compl x??!y);%>

На конкурсе Obfuscated C в 90-м году победил как раз код, активно использующий триграфы. Но сейчас на этом конкурсе тригафы использовать не рекомендуется.

Изуродовать код до неузнаваемости можно не только с помощью триграфов и диграфов. Если лгать в комментариях, придумывать дурацкие названия переменным и в усмерть все задефайнить, то код будет очень сложно читать и поддерживать. О чем очень убедительно рассказывается на сайте Unmaintainable code, причем не только про C/С++, там есть правила, применимые к любым языкам программирования, есть правила по отдельным языкам. За примерами совершенно невообразимого кода лучше сходить на сайт упомянутого уже конкурса Obfuscated C или на страничку The Daily WTF, где ежедневно публикуются реальные примеры кода, вызывающие ужас и отвращение.

Ссылки по теме:
comp.lang.c++.moderated printf() question
Obfuscated C
The Daily WTF

8 коммент.:

Kirill Belokurov комментирует...

С g++ ошибиться стало немного сложнее, сейчас при обнаружении триграфов он выводит (в примере триграф "??=" ):

warning: trigraph ??= ignored, use -trigraphs to enable

Prokrust комментирует...

Небольшое исследование
Все ключи по умолчанию
1. MinGW gcc (версия 3.4.2) выдает warning при обнаружении триграфов и игнорирует их.
2. MinGW gcc-2 (версия 2.95.3-8) warning не выводит и также игнорирует их.
3. Microsoft С++ Toolkit 2003 (версия 13.10.3052) компилит и выполняет триграфы по стандарту.
Выводит warning предупреждая что однострочный коментарий переходит на следущую строку.
4. OpenWatcom 1.3 работает также как и микрософтский.
5. Borland C++ (версия 5.5.1) ничего не знает о триграфах.
6. Digital Mars Compiler версии 8.42n ничего не знает о триграфах.
7. Lcc версии 3.8 работает
аналогично микрософтскому.
8. Ch интерпретатор версии 5.0.0 игнорирует триграф в коментарии (коммент остается однострочным) и выполняет в символьной строке.

Артём Сапегин комментирует...

Не знал об этом. Спасибо.

V.A.KeRneL комментирует...

Небольшая, но неприятная для не очень внимательных новичков, ачипятка:
...
??/
...

Очевидно, имелось в виду:
...
??/ \
...

Alena комментирует...

Небольшая, но неприятная для не очень внимательных новичков, ачипятка:

Спасибо, поправила.

Анонимный комментирует...

про *графы впервые узнал здесь :) опыт работы с С++, правда, совсем небольшой, но удивляюсь, как я на эти грабли не натыкался? Такое количество разнообразных три/диграфов...

Илья Весенний комментирует...

Предлагаю не очень изящный, но всё же пример запутывания - надеюсь, предостережёт кого-нибудь от попыток коряво "сделать красивое форматирование" :)

Руслан комментирует...

Именно у Саттера об этом узнал впервые. C++ - язык с юмором :)