Диграф и триграф не могут работать вместе?

Я изучаю орграф и триграф, и вот код, который я не могу понять. (Да, я признаю, что это очень некрасиво.)

Этот код может скомпилировать:

#define _(s) s%:%:s

main(_(_))
<%
    __;
%>t

Этот код также может компилироваться:

#define _(s) s??=??=s

main(_(_))
<%
    __;
%>

Однако ни один из следующих двух фрагментов кода не может быть скомпилирован:

#define _(s) s%:??=s

main(_(_))
<%
    __;
%>

А также

#define _(s) s??=%:s

main(_(_))
<%
    __;
%>

Это меня смущает: так как первые два фрагмента кода могут компилироваться, я предполагаю, что расширение digraph и trigraph оба имеют место перед расширением макроса. Так почему же он не может скомпилироваться, когда орграф и триграф используются вместе?

2 ответа

Решение

Диграфы и триграфы совершенно разные. Триграфы заменяются во время фазы 1 перевода, [см. Примечание 1], то есть до разделения исходного кода на токены. Органы - это токены, которые являются альтернативным написанием для других токенов, поэтому они не имеют смысла до тех пор, пока источник не будет разделен на токены. (Слово "орграф" не очень точное; оно используется, потому что оно напоминает "триграф", но набор орграфов включает в себя %:%: который состоит из четырех символов.)

Так ??= заменяется на # перед анализом любого токена. Но %: это просто знак, имеющий то же значение, что и #,

Более того, %:%: это токен с тем же значением, что и ##, Но %:# это два жетона (%: а также #), что недопустимо, так как оператор stringify (будь то пишется %: или же #) может сопровождаться только параметром макроса. [См. Примечание 2] И оно не станет менее незаконным, если # были результатом замены триграфа.

Одно важное различие между орграфами и триграфами, о чем свидетельствует веселый фрагмент в ответе chqrlie, состоит в том, что триграфы также работают в строках. Диграфы позволяют вам писать код на C, даже если на клавиатуре отсутствуют скобки и октоторпи, но они не помогают печатать эти символы.


Примечания (Стандартные цитаты):

  1. §5.1.1.2, Этапы перевода, пункт 1:

    Приоритет среди синтаксических правил перевода определяется следующими этапами.

    1. Многобайтовые символы физического исходного файла отображаются, в зависимости от реализации, в исходный набор символов (ввод символов новой строки для индикаторов конца строки), если это необходимо. Триграфные последовательности заменяются соответствующими односимвольными внутренними представлениями.
  2. §6.10.3.2, Оператор #, параграф 1:

    За каждым # токеном предварительной обработки в списке замены для функционально-подобного макроса должен следовать параметр в качестве следующего токена предварительной обработки в списке замены.

Для академической стороны, посмотрите на хорошо документированный ответ Ричи.

Что касается здравого смысла, если вы уже недостаточно хорошо владеете C, орграфы и триграфы совершенно бесполезны, и вам даже не следует тратить время на эту тему. Они были изобретены как способ поддержки не-американских 7-битных наборов символов, которые все еще использовались в 1980-х годах на мэйнфреймах и некоторых миникомпьютерах. В этих наборах символов отсутствовали некоторые знаки препинания, необходимые для языка Си, такие как #, {, } и т.п.

Даже в тех системах, которые я использовал некоторое время, триграфы никогда не использовались, потому что существовали безобразные прагматические альтернативы: во французских системах такие буквы с акцентом, как é а также è были напечатаны, но будет интерпретироваться компилятором C как { а также }, Это сделало программирование на C неясным и подтолкнуло многих программистов переключиться на американскую QWERTY-клавиатуру и Locale (или эквивалент).

Это в прошлом, только исторический интерес, и вы никогда не увидите их в действии, кроме опечаток, запутывания и неприятных вопросов для интервью.

Что касается последнего, я не могу удержаться от публикации этого:

Я не могу получить fnmatch чтобы проверить мой шаблон даты, даже если я ввожу правильную дату, что не так с этим кодом:

#include <stdio.h>
#include <fnmatch.h>
int main() {
    char date[16] = "01/01/1988";
    if (fnmatch("??/??/????", datebuf, 0))
        printf("invalid date format\n");
    return 0;
}
Другие вопросы по тегам