Значение символьных литералов, содержащих триграфы для непредставимых символов

В компиляторе C, который использует ASCII в качестве набора символов, значение символьного литерала '??<' будет эквивалентно значению '{', то есть 0x7B. Каково будет значение этого литерала для компилятора, чей набор символов не имеет символ {?

Вне строкового литерала компилятор может сделать вывод, что ??< должен иметь то же значение, что и символ открывающей скобки, даже если в наборе символов компилятора нет открывающей скобки персонаж. Действительно, вся цель триграфов состоит в том, чтобы разрешить использование последовательностей представимых символов вместо символов, которые не могут быть представлены. Однако спецификация требует, чтобы триграфы обрабатывались даже внутри строковых литералов, что меня озадачило. Если набор символов компилятора включает символ {, компилятор может позволить '{' быть представленным как '??<', но набор символов включает {. Я не вижу причин, по которым программист просто не использовал бы это. Однако, если набор символов не включает {, что может показаться единственной причиной использования триграфов в первую очередь, каким представимым символом компилятор должен заменить ??<?


person supercat    schedule 26.08.2014    source источник
comment
Набор символов может содержать {, но набрать { с помощью клавиатуры, используемой для написания программы, может быть непросто или даже невозможно.   -  person Mankarse    schedule 26.08.2014
comment
То же самое можно сказать и о многих тысячах символов, которые можно включить в символьные или строковые литералы; Я не уверен, что в этом отношении есть что-то особенное в { или любом другом символе триграфа. Если набор символов известен, можно просто использовать 0x7B или '\x7B, даже если нельзя набрать {. А для строковых (по сравнению с символьными) литералов макрос stringize, вероятно, мог бы дать более привлекательные результаты: #define LBR __stringize(??<) определил бы LBR как "{" [каким бы ни был символ {].   -  person supercat    schedule 26.08.2014
comment
@supercat Это одно разумное решение, но комитет по стандартизации C89 выбрал другое решение.   -  person Potatoswatter    schedule 26.08.2014
comment
'\x7B' и '??<' разные. Первый имеет значение 123, второй код символа { (который отличается для систем, отличных от ASCII). Вы не могли бы написать переносимый код, выполняющий последнее, без клавиатуры с клавишей {.   -  person mafso    schedule 26.08.2014
comment
@mafso: Что касается 0x7B, я сказал, известен ли набор символов. В противном случае, какие-либо триграфы, кроме ??/, предоставляют какую-либо функциональность, которую нельзя было бы реализовать, указав файл .h с макросами для символов [например, #define __LBR {/#define __clbr 0x7B/#define __SLBR __STRINGIZE(<:)]?   -  person supercat    schedule 26.08.2014
comment
Набор символов никогда не известен для строго соответствующего кода. Возможно, вы хотите ориентироваться на системы ASCII и EBCDIC и писать код с помощью клавиатуры, в которой отсутствуют определенные клавиши. О вашем вопросе о макросе: Да, кажется возможным, по приведенной выше ссылке: Некоторые пользователи могут захотеть определить макросы предварительной обработки для некоторых или всех последовательностей триграфов. Вы можете написать этот заголовок самостоятельно, если хотите, наоборот было бы невозможно, поэтому выбранный путь является, по крайней мере, более гибким.   -  person mafso    schedule 26.08.2014
comment
@mafso: в приведенном обосновании говорится о наборах символов, которые не имеют определенных глифов. Я ничего не вижу в простоте набора текста. Если кто-то использует систему, в которой 0x7B сопоставлено с é, а 0x7D с è, и в которой нет имеет глифов для { и }, я вижу, что int main(void) <: doSomething(); :> может быть лучше, чем int main(void) é doSomething(); é. Я предполагаю, что "l'??<l??>ve", вероятно, будет отображаться как "l'éléve", а не "l'{l}ve" на такой машине, но я не знаю ничего в спецификации, где бы это говорилось.   -  person supercat    schedule 26.08.2014
comment
Ты прав. Хотя триграфы могут быть полезны для клавиатур, в которых отсутствуют соответствующие клавиши, похоже, это не повод включать их в язык. И я не думаю, что стандарт C что-то говорит о представлении, см., например, 2.2.1 обоснования [...] обычная японская практика использования глифа ¥ для символа C \ совершенно законна.< /я>   -  person mafso    schedule 26.08.2014
comment
Вернемся к вашему актуальному вопросу: возможно, его можно заменить (в строковой константе) всем, что будет распознано как соответствующий символ. Например, подумайте о генерации кода: вывод printf("int main(void) ??< ??>??/n"); должен компилироваться на этой платформе.   -  person mafso    schedule 26.08.2014
comment
@mafso: это имело бы смысл; Интересно, есть ли что-то, что официально определяет вещи в таких терминах (например, утверждение, что должен существовать однобайтовый символ, который компилятор будет распознавать синтаксически способом, определенным для {, и ??< должен расширяться до этого символа). Если вы сможете найти что-нибудь, что четко указывает на это, и напишите ответ, я приму это.   -  person supercat    schedule 26.08.2014


Ответы (2)


Когда речь заходит об окружении, особенно о файлах, стандарт C намеренно становится довольно расплывчатым. В отношении триграфов и кодирования соответствующих им символов даются следующие гарантии:

C11 (n1570) 5.1.1.2 p1 («Этапы перевода») [выделение. мой]

  1. Многобайтовые символы физического исходного файла сопоставляются способом, определяемым реализацией, с исходным набором символов (введение символов новой строки для индикаторов конца строки), если это необходимо. Последовательности триграфов заменяются соответствующими односимвольными внутренними представлениями.

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

Если записать в текстовый поток, он может быть преобразован (как я читал, возможно, обратно в последовательность триграфа, если базовая кодировка не имеет кодировки для определенного символа). Его можно прочитать снова, и он должен сравниваться как равный, если он считается печатным символом. Там же. 7.21.2 п2:

[…] Данные, прочитанные из текстового потока, обязательно будут сравниваться с данными, ранее записанными в этот поток, только если: данные состоят только из печатных символов и управляющих символов, горизонтальной табуляции и новой строки; символу новой строки не предшествует пробел; и последний символ является символом новой строки. […]

Там же. 7.4 п3:

Термин «печатный символ» относится к элементу набора символов, зависящих от локали, каждый из которых занимает одну позицию печати на устройстве отображения; термин «управляющий символ» относится к члену набора символов, зависящему от локали, который не является печатным символом.*) Все буквы и цифры являются печатными символами.

*) В реализации, использующей семибитный набор символов US ASCII, печатными являются символы, значения которых лежат в диапазоне от 0x20 (пробел) до 0x7E (тильда); управляющие символы — это символы, значения которых лежат в диапазоне от 0 (NUL) до 0x1F (US), и символ 0x7F (DEL).

И для бинарных потоков там же. 7.21.2 п3:

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

В комментариях выше возник вопрос, если

printf("int main(void) ??< ??>\n");     // (1) 
printf("int main(void) ?\?< ?\?>\n");   // (2)

всегда работает для генерации кода, и вывод этого оператора гарантированно компилируется. Я не смог найти нормативную ссылку, требующую, чтобы isprint('??<') и т. д. (для (1)) или даже isprint('<') и т. д. (для (2)) возвращали ненулевое значение, но обоснование C89 о потоках гласит:

Набор символов, который необходимо сохранить в текстовом потоке ввода-вывода, необходим для написания программ на C; цель состоит в том, чтобы Стандарт позволял писать транслятор C максимально переносимым способом. Управляющие символы, такие как backspace, для этой цели не требуются, поэтому их обработка в текстовых потоках не обязательна.

Когда '??<' и т. д. записываются в двоичный поток, он должен отображаться в один байт, печататься как таковой, быть уникальным и отличимым от любого другого базового символа и сравниваться с '??<' при обратном чтении.


Связано: обоснование C89 о триграфах .

person mafso    schedule 29.08.2014
comment
Спасибо. Таким образом, система может произвольно выбирать коды символов для ??< и '??>', но они должны быть разными печатными символами. Мне любопытно, что могли бы сделать любые компиляторы C для Commodore 64; Я думаю, что были некоторые, но у этой машины не было глифов в форме ~, \ , { или }; и единственными вещами, напоминающими | и _, были символы рисования прямоугольника (вертикальная линия по центру и горизонтальная линия внизу окна). Ascii 0x5E был стрелкой вверх (достаточно близко к ^, чтобы просто называть его так), но 0x5F был стрелкой назад. Если бы я разрабатывал компилятор для этой системы... - person supercat; 29.08.2014
comment
... Я бы, вероятно, интерпретировал нижний символ блока как синоним стрелки назад в идентификаторах, но не литералов, и, вероятно, принял бы некоторые символы рисования блока как синоним фигурных скобок или трубы (их было легко набирать на клавиатуре). Я бы, вероятно, принял £ как синоним обратной косой черты (это код 0x5C), поскольку я не могу придумать никакого другого графического символа, который был бы действительно лучше. Не уверен насчет тильды; может быть, верхний символ (поскольку его значение похоже на верхнюю черту в описаниях цифровых сигналов). - person supercat; 29.08.2014
comment
Я не уверен, что они печатают символы. Обоснование говорит, что это предназначено (или, по крайней мере, они могут быть записаны и прочитаны из текстового потока), поэтому в некотором смысле они должны быть пригодны для печати. С другой стороны, только цифры и буквы должны быть печатными символами. - person mafso; 29.08.2014
comment
Ваш пример C64 хорошо показывает, почему триграфы могут быть удобны: предположим, вы программируете на этой машине. Вы могли использовать упомянутые вами символы непосредственно в исходном коде. Если бы вы сейчас захотели перенести свой код на другую машину (скажем, с использованием UTF-8), все символы, отличные от ISO646, были бы неправильно преобразованы преобразователем C64 в UTF8, но триграфы были бы преобразованы правильно (в триграфы). . - person mafso; 29.08.2014
comment
Действительно, я бы рассматривал это как основу для поддержки триграфов вне кавычек, и я не возражаю против них (хотя ??< и ??> избыточны, а ИМХО значительно превосходят <: и :>). Однако единственная выразительность, полученная при анализе триграфов в кавычках, — это возможность использовать символы обратной косой черты, такие как "??/n", и я думаю, что есть лучшие способы добиться этого [например, укажите, что если строковому литералу предшествует решетка (возможно, триграф) и другой специальный символ, этот символ заменит обратную косую черту до следующей кавычки. Таким образом... - person supercat; 29.08.2014
comment
...char * foo = #$"foo\bar$n" установит foo равным строке, содержащей символ обратной косой черты и новую строку]. Если компилятор использует нечетные наборы символов и для фигурных скобок, которые нельзя надежно преобразовать в ASCII, использование тех же символов в printf("int main() ??<doSomething();??>;"); имеет смысл, хотя любые проблемы, которые возникнут при переносе кода при использовании printf("int main() ┤doSomething();├");, в равной степени применимы к вывод кода триграфа, в то время как printf("int main() <: doSomething();:>"); не будет проблем с переводом ни кода, ни вывода. - person supercat; 29.08.2014
comment
Хм... Я не понимаю, почему проблемы с printf("int main() ┤doSomething();├"); также относятся к printf("int main() ??<doSomething();??>;");... Когда я конвертирую их на свою машину (используя UTF-8), вывод первого не компилируется, но вывод из последних делает. - person mafso; 29.08.2014
comment
На вашей машине последний код выведет int main {doSomething();}, но на машине, которая использует и в качестве фигурных скобок, он выведет int main ┤doSomething();├. Между прочим, я бегло взглянул на компиляторы C64 C, и похоже, что они имеют встроенные редакторы, которые перепрограммируют набор символов, чтобы включить символы ASCII, что теперь заставляет меня задуматься о том, как следует интерпретировать строковые литералы. C64 имеет два выбираемых предварительно загруженных набора символов; один определяет 0x53 и 0x73 как S и ; другой как s и S [именно в таком порядке]. В ASCII это S и s [другой порядок]. - person supercat; 29.08.2014

Каково будет значение этого литерала для компилятора, в наборе символов которого нет символа {?

Такого (соответствующего) компилятора нет. { является частью базового исходного набора символов (5.2.1/3 в C99, [lex.charset]/1 в C++ 11). Базовый набор символов выполнения (то, что программа использует во время выполнения) должен содержать как минимум все элементы базового исходного набора символов (тот же 5.2.1/3). в C99, [lex.charset]/3 в C++11).

Как отмечает @Mankarse, триграфы были изобретены не для поддержки компиляторов, в которых отсутствовали определенные символы (опять же, таких компиляторов нет), а для поддержки людей, печатающих на клавиатурах, на которых не было клавиш, необходимых для ввода этих символов.

person Igor Tandetnik    schedule 26.08.2014
comment
[lex.charset] — это перекрестная ссылка C++. Этот вопрос не о C++. Соответствующий параграф C11 — §5.2.1/3. - person Potatoswatter; 26.08.2014
comment
Кроме того, требование как в C, так и в C++ является обязательным, то есть абсолютным требованием, более сильным, чем следует. - person Potatoswatter; 26.08.2014
comment
@Potatoswatter: Ах, действительно, я не обращал внимания и просто предположил C++. Я исправлю ответ в ближайшее время. - person Igor Tandetnik; 26.08.2014
comment
Где вы прочитали такое обоснование триграфов? Обоснование, приведенное в комментарии к вопросу, предполагает, что цель состояла в том, чтобы разрешить системы, которые имели ограниченное количество глифов, которые они могли отображать или печатать, и которые присваивали коды символов глифам, таким как é и è, а не { и }. - person supercat; 26.08.2014
comment
@supercat Диграфы и триграфы: базовый набор символов языка программирования C является подмножеством Набор символов ASCII, включающий девять символов, не входящих в инвариантный набор символов ISO 646. Это может создать проблему для написания исходного кода, когда используемая кодировка (и, возможно, клавиатура) не поддерживает ни один из этих девяти символов. Комитет ANSI C изобрел триграфы как способ ввода исходного кода с помощью клавиатуры, поддерживающей любую версию набора символов ISO 646. - person Igor Tandetnik; 26.08.2014
comment
@IgorTandetnik: И, возможно, клавиатура. Это означало бы, что проблема заключается не только в наборе рассматриваемых символов — глифы не существуют в кодировке символов. Машина, которая преобразует 0x7B и 0x7D в é и è, вероятно, сможет напечатать é и è, но может не иметь возможности напечатать или отобразить { и }, или она может сопоставить { и } с 0xFB и 0xFD [у меня есть видели машины, где 0x20-0x7E визуализируются с использованием одного выбираемого набора символов, а 0xA0-0xFE визуализируются как другой]. Я предполагаю, что даже на машине, где 0xFB и 0xFD нужны для { и } - person supercat; 26.08.2014
comment
@IgorTandetnik: поскольку 0x7B и 0x7D отображаются как é и è, компилятор, вероятно, отобразит ??< и ??> как é и è, но я не знаю ничего в спецификации, где бы это говорилось. - person supercat; 26.08.2014
comment
@supercat Компилятор ничего не отображает. Я не уверен, о чем вы говорите. Если вы говорите о кодировании физического файла на диске, то 5.1.1.2/1 многобайтовые символы физического исходного файла сопоставляются способом, определяемым реализацией, с исходный набор символов (выделено мной). Я предполагаю, что возможно, помимо клавиатуры, когда-то существовали текстовые редакторы, неспособные представлять { (сохранение файлов в кодировке ISO 646), и триграфы были изобретены для поддержки написания кода в этих редакторах. Именно текстовые редакторы отображают символы в текстовом файле, а не компиляторы. - person Igor Tandetnik; 26.08.2014
comment
@IgorTandetnik: Исторически за отрисовку персонажей отвечало оборудование дисплея. Если бы проблема заключалась просто в клавиатуре, программист мог бы просто написать программу, которая переводила бы альтернативные символы или последовательности символов в набор символов C и загружала бы программу через это перед тем, как передать ее компилятору C. Кроме того, приведенная выше цитата относится к многобайтовым символам, что является отдельной проблемой. - person supercat; 26.08.2014