среда, февраля 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 комментариев:

  1. Анонимный1/2/06 18:44

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

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

    ОтветитьУдалить
  2. Анонимный2/2/06 12:13

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

    ОтветитьУдалить
  3. Анонимный1/3/06 20:18

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

    ОтветитьУдалить
  4. Анонимный30/10/06 04:21

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

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

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

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

    ОтветитьУдалить
  6. Анонимный18/4/07 13:22

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

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

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

    ОтветитьУдалить