Что это ??!??! оператор делать в C?

1990

Я видел строку C, которая выглядела так:

!ErrorHasOccured() ??!??! HandleError();

Он правильно скомпилирован и работает нормально. Кажется, что он проверяет, произошла ли ошибка, и если она есть, то обрабатывает ее. Но я не совсем уверен, что он на самом деле делает или как он это делает. Похоже, программист пытается выразить свои чувства по поводу ошибок.

Я никогда раньше не видел ??!??!ни одного языка программирования, и я не могу найти документацию для него нигде. (Google не помогает с поисковыми терминами, как ??!??!). Что это делает и как работает пример кода?

Питер Олсон
источник
44
@PeterOlson, как вы !ErrorHasOccurred() ??!???! HandleError();собираетесь компилировать? Это ??! ??? !. Доказывает смысл?
CVn
31
Я предлагаю вам прочитать на чистый код. ErrorHasOccured () следует изменить на ErrorHasNotOccured (), чтобы очистить восклицательный знак ... у кого есть время, чтобы понять все эти операторы ??!
KadekM
17
Я предпочитаю ErrorHasOccured() && HandleError()себя. Это также, как это делает Луа.
Хьюго Цинк
76
@KadekM, перемещение отрицания в имя функции не способствует чистому коду, скорее наоборот.
marcelm
14
Примечание для тех, кто попал сюда после битвы до смерти с помощью своей поисковой системы: SymbolHound может помочь с символическим поиском.
Якоб

Ответы:

1579

??!это триграф, который переводится как |. Так оно говорит:

!ErrorHasOccured() || HandleError();

который из-за короткого замыкания эквивалентен:

if (ErrorHasOccured())
    HandleError();

Гуру недели (имеет дело с C ++, но актуально здесь), где я поднял это.

Возможное происхождение триграфов или, как указывает @DwB в комментариях, более вероятно из-за сложности EBCDIC (опять же). Эта дискуссия на форуме разработчиков IBM, похоже, подтверждает эту теорию.

Из ISO / IEC 9899: 1999 §5.2.1.1, сноска 12 (h / t @ Random832):

Последовательности триграфа позволяют вводить символы, которые не определены в наборе инвариантных кодов, как описано в ИСО / МЭК 646, который является подмножеством семибитного набора кодов США ASCII.

user786653
источник
378
Изначально триграфы были нужны в том случае, если на клавиатуре не было, например, «|» символ. Здесь это либо умышленно раздражает программиста, либо какая-то странная «особенность» редактора
Мартин Беккет
36
Да, это эквивалентно if (ErrorHasOccured()) HandleError(). К счастью, вы обычно сталкиваетесь с этой идиомой в perl-коде.
user786653
22
Это не обязательно EBCDIC - набор символов, для которых требуются триграфы, почти точно совпадает с набором символов, которые не являются инвариантными в ISO-646 (то есть в старых «национальных стандартах ascii»).
Random832
52
Прекрасно читаемая альтернатива ErrorHasOccurred() && HandleError();: если вы привыкли к сценариям оболочки. :)
Ям Маркович
18
Прочитайте его как «либо нет ошибкиHasOcurcted, либо вы должны HandleError», @SparkyRobinson.
Омар Антолин-Камарена
453

Ну, почему это вообще существует, вероятно, отличается от того, почему оно существует в вашем примере.

Все началось полвека назад с перепрофилирования терминалов связи в качестве компьютерных пользовательских интерфейсов. В начальную эпоху Unix и C это был телетайп ASR-33.

Это устройство было медленным (10 cps), шумным и уродливым, и его представление набора символов ASCII закончилось на 0x5f, поэтому у него не было (посмотрите на рис.) Ни одного из ключей:

{ | } ~ 

Триграфы были определены для решения конкретной проблемы. Идея заключалась в том, чтобы программы на C могли использовать подмножество ASCII, найденное на ASR-33, и в других средах, в которых отсутствуют высокие значения ASCII.

Ваш пример на самом деле два ??!, каждый смысл |, так что результат ||.

Тем не менее, люди, пишущие код на C почти по определению, имели современное оборудование, 1 так что я думаю: кто-то хвастается или развлекается, оставляя в коде какое-то пасхальное яйцо, которое вы сможете найти.

Это действительно сработало, это привело к чрезвычайно популярному вопросу SO.

ASR-33 телетайп

                                            ASR-33 телетайп


1. В этом отношении триграфы были придуманы комитетом ANSI, который впервые встретился после того, как C добился безудержного успеха, поэтому ни один из оригинальных кодов или кодеров C не использовал бы их.

DigitalRoss
источник
18
Это не единственный случай отсутствия символов в клавиатуре и наборе символов. Commodore 64, вероятно, будет более знакомым многим людям в возрасте 30 лет и старше - отображаемым наборам символов не хватает фигурных скобок (и, вероятно, столбца и тильды тоже) - в этом случае, поскольку «ASCII» не был ASCII , В ECMA-6 (почти всегда называемой ASCII, но не US-ASCII) было 18 кодов, специфичных для региона, но я не знаю, какими они были. Одно могу сказать точно - в британском "ASCII" #был заменен на £. В других регионах, возможно, у «ASCII» не было фигурных скобок и т. Д.
Steve314
7
Подобный набор символов ATASCII для 8-битных компьютеров Atari также не содержал {}, а также ~ и `.
Ден04
42
Смотрите эти две статьи Википедии. Я достаточно взрослый, чтобы помнить эру 7-битных национальных кодировок (хотя я уверен, что они все еще остаются в некоторых темных неослабых углах), и книга, из которой я впервые узнал C, сочла необходимым предупредить о возможность if (x || y) { a[i] = '\0'; }выглядеть не if (x öö y) ä aÄiÅ = 'Ö0'; åв той кодировке.
Ильмари Каронен
9
Другая интересная историческая заметка заключается в том, что Unix (которая была большой платформой, на которой работал C), возможно, была первой системой любого значения (и, возможно, первой в целом), в которой буквенные значения по умолчанию установлены в нижний регистр, а не в верхний регистр. Хотя я не видел своими глазами многих современных систем, я думаю, что это был настоящий признак изощренности. Помимо того, что Unix действительно является единственной достойной ОС, она также преобразует ваш верхний регистр в нижний, а не наоборот. Эти парни были действительно крутыми.
DigitalRoss,
16
Забавная история, которую я должен рассказать ... Компилятор XL Fortran для рабочей станции IBM RS / 6000 был разработан на основе компилятора XL C. В первых нескольких выпусках они случайно уходили при обработке триграфа, поэтому были некоторые законные последовательности символов Фортрана (в буквальной строке, IIRC), которые были неверно истолкованы как триграфы C, что привело к некоторым интересным ошибкам!
Фил Перри
166

Это C триграф . ??!есть |, так же ??!??!как и оператор||

Джоэл Фальку
источник
5
trigraph пришел из периода, когда у какой-то клавиатуры не было всех клавиш, которые они имеют сейчас. Это также помогает, когда некоторый текстовый редактор зарезервировал специальные символы для особых вещей. В основном это пережиток прошлого и способствующий викторине;)
Джоэл Фалькоу
5
Потому что некоторые клавиатуры, по-видимому, не имеют "|" поэтому у некоторых людей нет другого выбора, кроме как многократно нажимать на клавиатуру, пока не произойдет триграф, дающий им необходимые символы.
Сова
И тогда есть <iso646.h>заголовочный файл.
Дэвид Р. Триббл
149

Как уже говорилось ??!??!, по существу , два триграфы ( ??!и ??!снова) mushed вместе , которые заменяются переведенный на ||, то есть логическое ИЛИ , препроцессором.

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

Trigraph   Replaces

??(        [
??)        ]
??<        {
??>        }
??/        \
??'        ^
??=        #
??!        |
??-        ~

Источник: C: Справочное руководство 5-е издание

Таким образом, триграф, который выглядит как ??(??), в конечном итоге отобразится [], ??(??)??(??)будет заменен [][]и т. Д., Вы получите идею.

Так как триграфы заменяются во время предварительной обработки, вы можете использовать, cppчтобы самостоятельно просмотреть вывод, используя глупую trigr.cпрограмму:

void main(){ const char *s = "??!??!"; } 

и обрабатывать его с помощью:

cpp -trigraphs trigr.c 

Вы получите консольный вывод

void main(){ const char *s = "||"; }

Как вы можете заметить, опция -trigraphsдолжна быть указана, иначе cppбудет выдано предупреждение; это указывает на то, что триграфы остались в прошлом и не представляют никакой современной ценности, кроме как сбивать с толку людей, которые могут столкнуться с ними .


Что касается обоснования введения триграфов, то лучше понять, рассматривая раздел истории ИСО / МЭК 646 :

ИСО / МЭК 646 и его предшественник ASCII (ANSI X3.4) в значительной степени одобрили существующую практику в отношении кодировки символов в телекоммуникационной отрасли.

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

(акцент мой)

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

Димитрис Фасаракис Хиллиард
источник