Этот исходный код включает строку в C. Как это сделать?

106

Я читаю код эмулятора и возражаю против чего-то действительно странного:

switch (reg){
    case 'eax':
    /* and so on*/
}

Как это возможно? Я думал, что можно только switchна целочисленных типах. Происходят ли какие-то макросы?

Ян Колтон
источник
29
это не строка, 'eax'и он перечисляет постоянное целочисленное значение
P__J__ 07
12
Одинарные кавычки, а не двойные. Символьная константа повышается до int, поэтому она разрешена. Однако значение многосимвольной константы определяется реализацией, поэтому код может работать не так, как ожидалось, в другом компиляторе. Например, eaxможет быть 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, или что - то еще.
Дэвислор 08
2
@Davislor: учитывая имя переменной reg и тот факт, что eax является регистром x86, я предполагаю, что поведение, определяемое реализацией, должно было быть нормальным, потому что оно одинаково везде, где оно используется в коде. Пока 'eax' != 'ebx', конечно, он не подходит только для одного или двух ваших примеров. Хотя где-то может быть какой-то код, который фактически предполагает *(int*)("eax") == 'eax'и, следовательно, не работает в большинстве ваших примеров.
Стив Джессоп
2
@SteveJessop Я согласен с тем, что вы говорите, но существует реальная опасность, что кто-то может попытаться скомпилировать код на другом компиляторе, даже для той же архитектуры, и получить другое поведение. Например, 'eax'можно сравнить «равно» 'ebx'или «с» 'ax', и оператор switch не будет работать должным образом.
Дэвислор
1
Вся эта тайна была бы быстро развеяна, если бы вы посмотрели / показали нам тип данных reg.
тыс

Ответы:

146

(Только вы можете ответить на вопрос о «обмане с макросами» - если вы не вставите больше кода. Но здесь не так уж много макросов для работы - формально вам не разрешено переопределять ключевые слова ; поведение при выполнении этого не определено.)

Чтобы добиться удобочитаемости программы, остроумный разработчик использует поведение, определяемое реализацией . 'eax'это не строка, а константа несколько символов . Очень внимательно обратите внимание на одинарные кавычки вокруг eax. Скорее всего, intв вашем случае это дает вам уникальную комбинацию символов. (Довольно часто каждый символ занимает 8 бит в 32 бите int). И каждый знает , что вы можете switchна int!

Наконец, стандартная ссылка:

Стандарт C99 гласит:

6.4.4.4p10: «Значение целочисленной символьной константы, содержащей более одного символа (например, 'ab') или содержащую символ или escape-последовательность, которая не отображается на однобайтовый символ выполнения, определяется реализацией. "

Вирсавия
источник
55
На всякий случай, если кто-то увидит это и запаникует, «определенная реализацией» должна работать и быть задокументирована вашим компилятором соответствующим образом (стандарт не требует, чтобы поведение было интуитивно понятным или чтобы документация была хорошей, но ...). Это «безопасно» для кодировщика, который полностью понимает, что они пишут, в отличие от «undefined».
Леушенко 07
7
@Justin Хотя могло бы, но это было бы довольно извращенно. Если он не делает того, что, скорее всего, предполагает ответ, следующая возможность, вероятно, заключается в том, что он просто использует первый символ и игнорирует остальные.
Barmar 07
5
@ZanLynx Я не уверен, но считаю, что эта функция задолго до Unicode и других стандартов MBCS. «Волшебные числа», которые выглядят как текст в дампах памяти, и идентификаторы блоков формата файла в стиле RIFF были первыми приложениями, о которых я знаю.
Рассел
16
@ jpmc26 Это не неопределенное поведение, оно определяется реализацией. Так что, если в документации компилятора не упоминаются демоны, ваш нос в безопасности.
Barmar 08
7
@ZanLynx: Боюсь, первоначальный замысел предшествовал Unicode, UTF-8 и любой кодировке многобайтовых символов почти на 20 лет. Многосимвольные константы были просто удобным способом выразить целые числа, представляющие группы по 2, 3 или 4 байта (в зависимости от размеров байтов и int). Несоответствия между реализациями и архитектурами заставили комитет объявить это как определенную реализацию , что означает, что нет переносимого способа вычислить значение 'ab'from 'a'и 'b'.
chqrlie 08
45

Согласно стандарту C (6.8.4.2 оператор switch)

3 Выражение каждой метки case должно быть целочисленным постоянным выражением ...

и (6.6 Постоянные выражения)

6 Целочисленное константное выражение должно иметь целочисленный тип и иметь только операнды, которые являются целочисленными константами, константами перечисления, символьными константами , выражениями sizeof, результаты которых являются целочисленными константами, и константами с плавающей запятой, которые являются непосредственными операндами приведения типов. Операторы приведения в целочисленном постоянном выражении должны преобразовывать только арифметические типы в целочисленные типы, кроме как части операнда в оператор sizeof.

Что теперь 'eax'?

Стандарт C (6.4.4.4 Символьные константы)

2 Целочисленная символьная константа - это последовательность одного или нескольких многобайтовых символов, заключенных в одинарные кавычки , как в 'x' ...

То 'eax'есть целочисленная символьная константа согласно параграфу 10 того же раздела

  1. ... Значение целочисленной символьной константы, содержащей более одного символа (например, 'ab') или содержащую символ или escape-последовательность, которая не отображается на однобайтовый символ выполнения, определяется реализацией.

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

Обратите внимание на то, что символьная константа (заключенная в одинарные кавычки) имеет тип intи отличается от строкового литерала (последовательность символов, заключенная в двойные кавычки), имеющего тип символьного массива.

Влад из Москвы
источник
12

Как говорили другие, это intконстанта, и ее фактическое значение определяется реализацией.

Я предполагаю, что остальная часть кода выглядит примерно так

if (SOMETHING)
    reg='eax';
...
switch (reg){
    case 'eax':
    /* and so on*/
}

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

В комментарии @Davislor перечисляет некоторые возможные значения для 'eax':

... 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, или что - то другое

Обратите внимание на первое потенциальное значение? Это просто 'e'игнорирование двух других персонажей. Проблема в том , программа , вероятно , использует 'eax', 'ebx'и так далее. Если все эти константы имеют то же значение, что и у 'e'вас

switch (reg){
    case 'e':
       ...
    case 'e':
       ...
    ...
}

Выглядит не очень хорошо, правда?

Хорошая особенность «определяемого реализацией» заключается в том, что программист может проверить документацию своего компилятора и посмотреть, делает ли он что-нибудь разумное с этими константами. Если да, то дома бесплатно.

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

Как отметил @zwol в комментариях, ситуация не так плоха, как я думал, в плохом случае код не компилируется. По крайней мере, это даст вам точное имя файла и номер строки проблемы. Все равно у вас не будет работающей программы.

Стиг Хеммер
источник
1
кроме какой-либо формы, assert('eax' != 'ebx'); //if this fails you can't compile the code because...может ли оригинальный автор что-нибудь сделать для предотвращения других сбоев компилятора без полной замены конструкции>
Дэн
6
Две метки case с одним и тем же значением являются нарушением ограничения (6.8.4.2p3: «... никакие два выражения констант case в одном операторе switch не должны иметь одинаковое значение после преобразования»), так что пока весь код обрабатывает значения этих констант как непрозрачные, это гарантирует либо работу, либо ошибку компиляции.
звол
Хуже всего то, что бедняга, компилирующий на другом компиляторе, вероятно, не увидит никаких ошибок времени компиляции (включение целых чисел - нормально); вместо этого
возникнут
1

Во фрагменте кода используется историческая странность, называемая многосимвольной константой , также называемая многосимвольной .

'eax' - целочисленная константа, значение которой определяется реализацией.

Вот интересная страница о нескольких символах и о том, как их можно использовать, но не следует:

http://www.zipcon.net/~swhite/docs/computers/languages/c_multi-char_const.html


Оглядываясь назад в зеркало заднего вида, вот как в оригинальном руководстве по C Денниса Ритчи из старых добрых времен ( https://www.bell-labs.com/usr/dmr/www/cman.pdf ) указаны символьные константы. .

2.3.2 Символьные константы

Символьная константа - это 1 или 2 символа, заключенные в одинарные кавычки '' '''. Внутри символьной константы одинарной кавычке должна предшествовать обратная косая черта '' \''. Некоторые неграфические символы и \сам символ "" "могут быть экранированы в соответствии со следующей таблицей:

    BS \b
    NL \n
    CR \r
    HT \t
    ddd \ddd
    \ \\

Escape '' \ddd'' состоит из обратной косой черты, за которой следуют 1, 2 или 3 восьмеричные цифры, которые используются для указания значения желаемого символа. Особым случаем этой конструкции является "" \0"(без цифры), обозначающий нулевой символ.

Символьные константы ведут себя точно так же, как целые числа (не, в частности, как объекты символьного типа). В соответствии со структурой адресации PDP-11 символьная константа длиной 1 имеет код для данного символа в младшем байте и 0 в старшем байте; символьная константа длиной 2 имеет код для первого символа в младшем байте и для второго символа в старшем байте. Символьные константы с более чем одним символом по своей сути зависят от машины, и их следует избегать.

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

chqrlie
источник