Dieser Quellcode schaltet eine Zeichenfolge in C ein. Wie funktioniert das?

106

Ich lese einen Emulatorcode durch und habe etwas wirklich Seltsames kontert:

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

Wie ist das möglich? Ich dachte man könnte nur switchauf ganzzahlige Typen. Gibt es Makrotricks?

Ian Colton
quelle
29
es ist nicht die Zeichenfolge 'eax'und es zählt konstanten ganzzahligen Wert auf
P__J__
12
Einfache Anführungszeichen, nicht doppelt. Eine Zeichenkonstante wird befördert int, daher ist dies legal. Der Wert einer Konstante mit mehreren Zeichen ist jedoch implementierungsdefiniert, sodass der Code auf einem anderen Compiler möglicherweise nicht wie erwartet funktioniert. Zum Beispiel eaxkönnte sein 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, oder etwas anderes.
Davislor
2
@Davislor: Angesichts des Namens der Variablen "reg" und der Tatsache, dass eax ein x86-Register ist, würde ich vermuten, dass das implementierungsdefinierte Verhalten in Ordnung sein sollte, da es überall im Code gleich ist. Nur solange 'eax' != 'ebx'natürlich, so scheitert es nur an einem oder zwei Ihrer Beispiele. Obwohl es möglicherweise irgendwo Code gibt, der tatsächlich davon ausgeht *(int*)("eax") == 'eax'und daher die meisten Ihrer Beispiele fehlschlägt.
Steve Jessop
2
@SteveJessop Ich bin nicht anderer Meinung als Sie, aber es besteht die reale Gefahr, dass jemand versucht, den Code auf einem anderen Compiler zu kompilieren, selbst für dieselbe Architektur, und ein anderes Verhalten erhält. Zum Beispiel 'eax'könnte ein Vergleich gleich 'ebx'oder gleich sein 'ax', und die switch-Anweisung würde nicht wie beabsichtigt funktionieren.
Davislor
1
All dieses Rätsel wäre schnell gelöst worden, wenn Sie uns den Datentyp der Registrierung nachgeschlagen / gezeigt hätten.
th

Antworten:

146

(Nur Sie können den Teil "Makro-Tricks" beantworten - es sei denn, Sie fügen mehr Code ein. Es gibt hier jedoch nicht viel, woran Makros arbeiten können. Formal dürfen Sie Schlüsselwörter nicht neu definieren . Das Verhalten dabei ist undefiniert.)

Um die Programmlesbarkeit zu erreichen, nutzt der witzige Entwickler das implementierungsdefinierte Verhalten . 'eax'ist keine Zeichenfolge, sondern eine Konstante mit mehreren Zeichen . Beachten Sie die einzelnen Anführungszeichen sehr sorgfältig eax. Höchstwahrscheinlich gibt es Ihnen intin Ihrem Fall eine, die für diese Kombination von Zeichen einzigartig ist. (Sehr oft belegt jedes Zeichen 8 Bit in 32 Bit int). Und jeder weiß, dass Sie switchauf einem können int!

Zum Schluss noch eine Standardreferenz:

Der C99-Standard sagt:

6.4.4.4p10: "Der Wert einer Ganzzahlzeichenkonstante, die mehr als ein Zeichen enthält (z. B. 'ab') oder ein Zeichen oder eine Escape-Sequenz enthält, die keinem Einzelbyte-Ausführungszeichen zugeordnet ist, ist implementierungsdefiniert. ""

Bathseba
quelle
55
Nur für den Fall, dass jemand dies sieht und in Panik gerät, muss "implementierungsdefiniert" funktionieren und von Ihrem Compiler in geeigneter Weise dokumentiert werden (der Standard verlangt nicht, dass das Verhalten intuitiv ist oder dass die Dokumentation gut ist, aber ...). Dies ist "sicher" für einen Codierer, der vollständig versteht, was er schreibt, im Gegensatz zu "undefiniert".
Leushenko
7
@ Justin Während es könnte, wäre das ziemlich pervers. Wenn es nicht das tut, was die Antwort vermuten lässt, ist die nächste Möglichkeit wahrscheinlich, dass es nur das erste Zeichen verwendet und den Rest ignoriert.
Barmar
5
@ZanLynx Ich bin nicht positiv, aber ich glaube, dass die Funktion lange vor Unicode und anderen MBCS-Standards liegt. "Magische Zahlen", die wie Text in Speicherauszügen aussehen, und Dateiformat-Chunk-IDs im RIFF-Stil waren die ersten mir bekannten Anwendungen.
Russell Borogove
16
@ jpmc26 Dies ist kein undefiniertes Verhalten, sondern implementierungsdefiniert. Wenn in der Compiler-Dokumentation keine Dämonen erwähnt werden, ist Ihre Nase sicher.
Barmar
7
@ZanLynx: Ich fürchte, die ursprüngliche Absicht liegt fast 20 Jahre vor Unicode, UTF-8 und jeder Multibyte-Zeichencodierung. Konstanten mit mehreren Zeichen waren nur eine praktische Möglichkeit, Ganzzahlen auszudrücken, die Gruppen von 2, 3 oder 4 Bytes darstellen (abhängig von der Byte- und Int-Größe). Inkonsistenzen zwischen Implementierungen und Architekturen führten dazu, dass das Komitee dies als definierte Implementierung deklarierte. Dies bedeutet, dass es keine tragbare Methode gibt, um den Wert von 'ab'from 'a'und zu berechnen 'b'.
Chqrlie
45

Nach dem C-Standard (6.8.4.2 Die switch-Anweisung)

3 Der Ausdruck jedes Falletiketts muss ein ganzzahliger konstanter Ausdruck sein ...

und (6.6 Konstante Ausdrücke)

6 Ein Ausdruck für eine ganzzahlige Konstante muss einen ganzzahligen Typ haben und darf nur Operanden haben, die ganzzahlige Konstanten, Aufzählungskonstanten, Zeichenkonstanten , die Größe von Ausdrücken sind, deren Ergebnisse ganzzahlige Konstanten sind, und schwebende Konstanten, die die unmittelbaren Operanden von Casts sind. Umwandlungsoperatoren in einem ganzzahligen konstanten Ausdruck konvertieren nur arithmetische Typen in ganzzahlige Typen, außer als Teil eines Operanden in die Größe des Operators.

Was ist nun? 'eax' ?

Der C-Standard (6.4.4.4 Zeichenkonstanten)

2 Eine Ganzzahlzeichenkonstante ist eine Folge von einem oder mehreren Multibyte-Zeichen in einfachen Anführungszeichen , wie in 'x' ...

So 'eax'ist eine ganzzahlige Zeichenkonstante gemäß Absatz 10 desselben Abschnitts

  1. ... Der Wert einer ganzzahligen Zeichenkonstante, die mehr als ein Zeichen enthält (z. B. 'ab') oder ein Zeichen oder eine Escape-Sequenz enthält, die keinem Einzelbyte-Ausführungszeichen zugeordnet ist, ist implementierungsdefiniert.

Nach dem erstgenannten Zitat kann es sich also um einen Operanden eines ganzzahligen konstanten Ausdrucks handeln, der als Fallbezeichnung verwendet werden kann.

Beachten Sie, dass eine Zeichenkonstante (in einfache Anführungszeichen eingeschlossen) vom Typ ist intund nicht mit einem Zeichenfolgenliteral (einer Folge von Zeichen in doppelten Anführungszeichen) identisch ist, das den Typ eines Zeichenarrays hat.

Vlad aus Moskau
quelle
12

Wie andere gesagt haben, ist dies ein int Konstante und ihr tatsächlicher Wert ist implementierungsdefiniert.

Ich gehe davon aus, dass der Rest des Codes ungefähr so ​​aussieht

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

Sie können sicher sein, dass 'eax' im ersten Teil den gleichen Wert wie 'eax' im zweiten Teil hat, also klappt alles, oder? ... falsch.

In einem Kommentar listet @Davislor einige mögliche Werte für 'eax' auf:

... 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, oder etwas anderes

Beachten Sie den ersten möglichen Wert? Das heißt 'e', die beiden anderen Zeichen werden ignoriert. Das Problem ist das Programm wahrscheinlich verwendet 'eax', 'ebx'und so weiter. Wenn alle diese Konstanten den gleichen Wert haben, den 'e'Sie am Ende haben

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

Das sieht doch nicht gut aus, oder?

Das Gute an "implementierungsdefiniert" ist, dass der Programmierer die Dokumentation seines Compilers überprüfen kann, um festzustellen, ob er mit diesen Konstanten etwas Sinnvolles macht. Wenn ja, frei zu Hause.

Das Schlimme ist, dass ein anderer armer Kerl den Code nehmen und versuchen kann, ihn mit einem anderen Compiler zu kompilieren. Sofortiger Kompilierungsfehler. Das Programm ist nicht portierbar.

Wie @zwol in den Kommentaren betonte, ist die Situation nicht ganz so schlimm wie ich dachte, im schlimmsten Fall wird der Code nicht kompiliert. Dadurch erhalten Sie mindestens einen genauen Dateinamen und eine Zeilennummer für das Problem. Trotzdem haben Sie kein Arbeitsprogramm.

Stig Hemmer
quelle
1
Abgesehen von irgendeiner Form assert('eax' != 'ebx'); //if this fails you can't compile the code because...gibt es irgendetwas, was der ursprüngliche Autor tun könnte, um andere Compilerfehler zu verhindern, ohne das Konstrukt vollständig zu ersetzen.
Dan Is Fiddling By Firelight
6
Zwei Fallbezeichnungen mit demselben Wert stellen eine Einschränkungsverletzung dar (6.8.4.2p3: "... keine zwei der Ausdrücke für Fallkonstanten in derselben switch-Anweisung dürfen nach der Konvertierung denselben Wert haben"), solange der gesamte Code vorhanden ist Wenn die Werte dieser Konstanten als undurchsichtig behandelt werden, funktioniert dies garantiert oder kann nicht kompiliert werden.
Zwol
Das Schlimmste ist, dass der arme Kollege, der auf einem anderen Compiler kompiliert, wahrscheinlich keinen Fehler bei der Kompilierung sieht (das Einschalten von Ints ist in Ordnung). Stattdessen treten Laufzeitfehler auf ...
Tucuxi
1

Das Codefragment verwendet eine historische Kuriosität, die als Zeichenkonstante mit mehreren Zeichen bezeichnet wird und auch als Zeichen mit mehreren Zeichen bezeichnet wird .

'eax' ist eine ganzzahlige Konstante, deren Wert durch die Implementierung definiert ist.

Hier ist eine interessante Seite über Multi-Zeichen und wie sie verwendet werden können, aber nicht sollten:

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


Wenn Sie weiter in den Rückspiegel zurückblicken, finden Sie im Original-C-Handbuch von Dennis Ritchie aus guten alten Zeiten ( https://www.bell-labs.com/usr/dmr/www/cman.pdf ) Zeichenkonstanten .

2.3.2 Zeichenkonstanten

Eine Zeichenkonstante besteht aus 1 oder 2 Zeichen in einfachen Anführungszeichen '. Innerhalb einer Zeichenkonstante muss vor einem einfachen Anführungszeichen ein Schrägstrich '' \'' stehen. Bestimmte nicht grafische Zeichen und '' \'' selbst können gemäß der folgenden Tabelle maskiert werden:

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

Das Escape- \dddZeichen '' '' besteht aus dem Backslash, gefolgt von 1, 2 oder 3 Oktalstellen, mit denen der Wert des gewünschten Zeichens angegeben wird. Ein Sonderfall dieser Konstruktion ist ''\0 '' (nicht gefolgt von einer Ziffer), die ein Nullzeichen angibt.

Zeichenkonstanten verhalten sich genau wie Ganzzahlen (insbesondere nicht wie Objekte vom Zeichentyp). In Übereinstimmung mit der Adressierungsstruktur des PDP-11 hat eine Zeichenkonstante der Länge 1 den Code für das gegebene Zeichen im Byte niedriger Ordnung und 0 im Byte hoher Ordnung; Eine Zeichenkonstante der Länge 2 hat den Code für das erste Zeichen im niedrigen Byte und den für das zweite Zeichen im höherwertigen Byte. Zeichenkonstanten mit mehr als einem Zeichen sind von Natur aus maschinenabhängig und sollten vermieden werden.

Der letzte Satz ist alles, woran Sie sich bei dieser merkwürdigen Konstruktion erinnern müssen: Zeichenkonstanten mit mehr als einem Zeichen sind von Natur aus maschinenabhängig und sollten vermieden werden.

chqrlie
quelle