Цей вихідний код перемикає рядок у C. Як це зробити?


106

Я читаю якийсь код емулятора, і я протиставив щось справді дивне:

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

Як це можливо? Я думав, ти можеш лише switchна цілісних типах. Чи відбувається якась макро-хитрість?


29
це не рядок, 'eax'і він перераховує постійне ціле значення
P__J__

12
Одиночні цитати, а не подвійні. Константа символів просувається до int, тому це законно. Однак значення констати багато символів визначено реалізацією, тому код може не працювати, як очікувалося, для іншого компілятора. Наприклад, eaxможе бути 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, або що - то ще.
Девіслор

2
@Davislor: з урахуванням назви змінної "reg" і того факту, що eax є реєстром x86, я б здогадався, що визначена реалізацією поведінка мала бути нормальною, оскільки вона однакова скрізь, де вона використовується в коді. До тих пір, поки 'eax' != 'ebx', звичайно, так це лише один чи два ваші приклади. Хоча десь може бути якийсь код, який насправді передбачає *(int*)("eax") == 'eax', і тому не дає більшості ваших прикладів.
Стів Джессоп

2
@SteveJessop Я не погоджуюся з тим, що ви говорите, але є реальна небезпека, що хтось може спробувати скласти код на іншому компіляторі, навіть для тієї ж архітектури, та отримати іншу поведінку. Наприклад, 'eax'може порівнюватись рівним 'ebx'або до 'ax', і оператор перемикання не буде працювати за призначенням.
Девіслор

1
Вся ця таємниця швидко була б розвіяна, якби ви подивились / показали нам тип даних reg.
тис

Відповіді:


146

(Тільки ви можете відповісти на частину "хитрості макро" - якщо не вставити більше коду. Але макросів тут не так багато - формально вам не дозволяється переглядати ключові слова ; поведінка при виконанні цього завдання не визначена.)

Щоб досягти читабельності програми, дотепний розробник використовує поведінку, визначену реалізацією . 'eax'- це не рядок, а константа багато символів . Зауважте дуже уважно окремі символи цитат навколо eax. Швидше за все, це дає вам intу вашому випадку унікальний для цієї комбінації символів. (Досить часто кожен символ займає 8 біт у 32 бітах int). І всі знають, що можна switchна int!

Нарешті, стандартне посилання:

Стандарт C99 говорить:

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


55
На всякий випадок, коли хтось бачить це і панікує, "що визначається реалізацією", потрібно працювати і бути документально оформленим вашим компілятором якось відповідним чином (стандарт не вимагає, щоб поведінка була інтуїтивно зрозумілою або документація була корисною, але ...). Це "безпечно" використовувати для кодера, який повністю розуміє, що вони пишуть, на відміну від "невизначеного".
Левшенко

7
@Justin Хоча це могло б, це було б дуже збочено. Якщо він не робить те, що пропонує відповідь, найімовірніше, наступна можливість, ймовірно, що він просто використовує перший символ і ігнорує решту.
Бармар

5
@ZanLynx Я не позитивний, але я вважаю, що ця функція давно передує Unicode та іншим стандартам MBCS. Перші програми, про які я знаю, були "магічні числа", схожі на текст у пам ятках пам'яті та ідентифікаторах файлового формату у стилі RIFF.
Рассел Борогов

16
@ jpmc26 Це не визначена поведінка, це визначено реалізацією. Тому, якщо документація на компілятор не згадує демонів, ваш ніс є в безпеці.
Бармар

7
@ZanLynx: Я боюся, що оригінальний намір передував Unicode, UTF-8 та будь-яке багатобайтове кодування символів майже 20 років. константа багато символів була просто зручним способом вираження цілих чисел, що представляють групи з 2, 3 або 4 байтів (залежно від розміру байтів та int). Невідповідності між реалізаціями та архітектурами змусили комітет оголосити це як визначене впровадженням , а це означає, що немає портативного способу обчислення значення 'ab'з 'a'і 'b'.
chqrlie

45

Відповідно до стандарту C (6.8.4.2 заява комутатора)

3 Вираз кожної мітки регістру має бути цілим постійним виразом ...

і (6.6 Постійні вирази)

6 Постійний вираз цілого числа має цілий тип і має операнди, які є цілими константами, константи перерахування, символьні константи , розміри виразів, результати яких є цілими константами, і плаваючі константи, які є безпосередніми операндами касти. Оператори викиду в цілочисленному постійному виразі перетворюють арифметичні типи лише на цілі типи, за винятком того, що є частиною операнда в оператор розміру.

Тепер що таке 'eax'?

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

2 Ціла константа символів - це послідовність одного або декількох багатобайтових символів, укладених в одинарні лапки , як у 'x' ...

Таким чином, 'eax'є цілою постійною символом відповідно до абзацу 10 того ж розділу

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

Отже, згідно з першою згаданою цитатою це може бути операнд цілого цілого постійного виразу, який може бути використаний як мітка регістру.

Зверніть увагу на те, що константа символів (укладена в одинарні лапки) має тип 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
Дві мітки регістру з однаковим значенням є порушенням обмеження (6.8.4.2p3: "... жодне два постійних вирази випадку в одному і тому ж вимиканні вимикача не повинно мати одне і те ж значення після перетворення"), доки весь код трактує значення цих констант як непрозорі, це гарантовано або працює, або неможливо компілювати.
zwol

Гірша частина полягає в тому, що поганий хлопець, що збирається на іншому компіляторі, ймовірно, не побачить жодної помилки часу компіляції (увімкнення ints нормально); натомість помилки під час виконання будуть
назрівати

1

Фрагмент коду використовує історичну незвичайність, яку називають постійною символом багато символів , яку також називають багатозначною .

'eax' це ціла константа, значення якої визначено реалізацією.

Ось цікава сторінка про багаточастотні символи та те, як їх можна використовувати, але не слід:

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


Оглянувшись далі в дзеркало заднього огляду, ось як в оригінальній інструкції C Dennis Ritchie зі старих часів ( 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
    \ \\

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

Константи символів поводяться точно як цілі числа (не, зокрема, як об'єкти типу символів). Відповідно до структури адресації PDP-11, константа символів довжиною 1 має код для даного символу в байті низького порядку та 0 у байті високого порядку; символьна константа довжиною 2 має код для першого символу в низькому байті та для другого символу в байті високого порядку. Константи символів, що мають більше одного символу, по суті залежать від машини, і цього слід уникати.

Остання фраза - все, що вам потрібно пам’ятати про цю цікаву конструкцію: константи символів, що мають більше одного символу, по суті залежать від машини, і цього слід уникати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.