Dieser Quellcode schaltet einen String in C ein. Wie macht er das?
Lesezeit: 8 Minuten
Ian Colton
Ich lese mir einen Emulatorcode durch und habe etwas wirklich Seltsames kontert:
switch (reg){
case 'eax':
/* and so on*/
}
Wie ist das möglich? Ich dachte, du könntest nur switch auf ganzzahlige Typen. Gibt es irgendwelche Makro-Tricks?
es ist nicht die Saite 'eax' und es zählt den konstanten ganzzahligen Wert auf
– 0___________
7. August 2017 um 15:35 Uhr
Einfache Anführungszeichen, keine doppelten. Eine Zeichenkonstante wird heraufgestuft int, also 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, eax könnte sein 0x65, 0x656178, 0x65617800, 0x786165, 0x6165oder etwas anderes.
– Davislor
8. August 2017 um 2:11 Uhr
@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. Genau so lange wie 'eax' != 'ebx', natürlich, also scheitert es nur an einem oder zwei Ihrer Beispiele. Obwohl es irgendwo Code geben könnte, der tatsächlich davon ausgeht *(int*)("eax") == 'eax'und versagt daher bei den meisten Ihrer Beispiele.
– Steve Jessop
8. August 2017 um 13:21 Uhr
@SteveJessop Ich bin mit dem, was Sie sagen, nicht einverstanden, aber es besteht die reale Gefahr, dass jemand versuchen könnte, den Code auf einem anderen Compiler zu kompilieren, selbst für dieselbe Architektur, und ein anderes Verhalten erhält. Zum Beispiel, 'eax' gleich vergleichen könnte 'ebx' oder zu 'ax'und die switch-Anweisung würde nicht wie beabsichtigt funktionieren.
– Davislor
8. August 2017 um 21:50 Uhr
All dieses Rätsel wäre schnell zerstreut worden, wenn Sie den Datentyp von reg nachgeschlagen/gezeigt hätten.
– ths
8. August 2017 um 22:43 Uhr
Bathseba
(Nur Sie können den Teil “Makrotricks” beantworten – es sei denn, Sie fügen mehr Code ein. Aber hier gibt es nicht viel, woran Makros arbeiten können – formal dürfen Sie nicht neu definieren Schlüsselwörter; das Verhalten dabei ist undefiniert.)
Um die Lesbarkeit des Programms zu erreichen, nutzt der geistreiche Entwickler aus Implementierung definiertes Verhalten. 'eax' ist nicht eine Zeichenfolge, aber a Konstante mit mehreren Zeichen. Beachten Sie sehr sorgfältig die einzelnen Anführungszeichen eax. Höchstwahrscheinlich gibt es Ihnen eine int in Ihrem Fall ist das einzigartig für diese Kombination von Zeichen. (Oft belegt jedes Zeichen in einer 32-Bit-Datei 8 Bit int). Und jeder weiß, dass du es kannst switch auf ein int!
Zum Schluss noch eine Standardreferenz:
Der C99-Standard sagt:
6.4.4.4p10: „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. “
Nur für den Fall, dass jemand das sieht und in Panik gerät, muss “implementierungsdefiniert” funktionieren und von Ihrem Compiler auf geeignete 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 Programmierer, der vollständig versteht, was er schreibt, im Gegensatz zu “undefiniert”.
– Leuschenko
7. August 2017 um 18:25 Uhr
@Justin Es wäre zwar möglich, aber das wäre ziemlich pervers. Wenn es nicht das tut, was die Antwort am wahrscheinlichsten vorschlägt, besteht die nächste Möglichkeit wahrscheinlich darin, dass es nur das erste Zeichen verwendet und den Rest ignoriert.
– Barmar
7. August 2017 um 21:54 Uhr
@ZanLynx Ich bin nicht sicher, aber ich glaube, dass die Funktion lange vor Unicode und anderen MBCS-Standards liegt. “Magische Zahlen”, die wie Text in Speicherabbildern aussehen, und Dateiformat-Chunk-IDs im RIFF-Stil waren die ersten Anwendungen, die mir bekannt sind.
– Russell Borogove
8. August 2017 um 3:55 Uhr
@ jpmc26 Dies ist kein undefiniertes Verhalten, es ist implementierungsdefiniert. Wenn die Compiler-Dokumentation also keine Dämonen erwähnt, ist Ihre Nase sicher.
– Barmar
8. August 2017 um 4:45 Uhr
@ZanLynx: Ich fürchte, die ursprüngliche Absicht liegt fast 20 Jahre vor Unicode, UTF-8 und jeder Multibyte-Zeichencodierung. Konstante 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 veranlassten das Komitee, dies als zu erklären Umsetzung definiertwas bedeutet, dass es keine portable Möglichkeit gibt, den Wert von zu berechnen 'ab' aus 'a' und 'b'.
– chqrlie
8. August 2017 um 6:41 Uhr
Vlad aus Moskau
Gemäß dem C-Standard (6.8.4.2 Die switch-Anweisung)
3 Der Ausdruck jedes Case-Labels muss ein ganzzahliger konstanter Ausdruck sein…
und (6.6 Konstante Ausdrücke)
6 Ein ganzzahliger konstanter Ausdruck muss einen ganzzahligen Typ haben und darf nur Operanden haben, die ganzzahlige Konstanten, Aufzählungskonstanten, Zeichenkonstanten, sizeof -Ausdrücke, deren Ergebnisse ganzzahlige Konstanten sind, und Gleitkommakonstanten, die die unmittelbaren Operanden von Umwandlungen sind. Cast-Operatoren in einem ganzzahligen konstanten Ausdruck konvertieren nur arithmetische Typen in ganzzahlige Typen, außer als Teil eines Operanden für den sizeof-Operator.
Was ist nun 'eax'?
Der C-Standard (6.4.4.4 Zeichenkonstanten)
2 Eine ganzzahlige Zeichenkonstante ist eine Folge von einem oder mehreren Multibyte-Zeichen, die in einfache Anführungszeichen eingeschlossen sindwie in ‘x’ …
So 'eax' ist eine ganzzahlige Zeichenkonstante gemäß Absatz 10 desselben Abschnitts
…Der Wert einer Integer-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 Case-Label verwendet werden kann.
Achten Sie darauf, dass eine Zeichenkonstante (in einfachen Anführungszeichen eingeschlossen) einen Typ hat int und ist nicht dasselbe wie ein Zeichenfolgenliteral (eine in doppelte Anführungszeichen eingeschlossene Folge von Zeichen), das den Typ eines Zeichenarrays hat.
Stig Hemmer
Wie andere gesagt haben, ist dies eine int konstant und ihr tatsächlicher Wert ist implementierungsdefiniert.
Ich nehme an, der Rest des Codes sieht ungefähr so aus
if (SOMETHING)
reg='eax';
...
switch (reg){
case 'eax':
/* and so on*/
}
Sie können sicher sein, dass ‘eax’ im ersten Teil den gleichen Wert hat wie ‘eax’ im zweiten Teil, also funktioniert alles, oder? … falsch.
In einem Kommentar listet @Davislor einige mögliche Werte für ‘eax’ auf:
… 0x65, 0x656178, 0x65617800, 0x786165, 0x6165oder etwas anderes
Beachten Sie den ersten potenziellen Wert? Das ist nur 'e', wobei die anderen beiden Zeichen ignoriert werden. Das Problem ist wahrscheinlich das Programm verwendet 'eax', 'ebx', usw. Wenn alle diese Konstanten den gleichen Wert haben wie 'e' du am Ende mit
switch (reg){
case 'e':
...
case 'e':
...
...
}
Das sieht nicht besonders gut aus, oder?
Das Gute an “implementierungsdefiniert” ist, dass der Programmierer die Dokumentation seines Compilers überprüfen und sehen kann, ob er mit diesen Konstanten etwas Sinnvolles macht. Wenn ja, Haus frei.
Das Schlimme ist, dass ein anderer armer Kerl den Code nehmen und versuchen kann, ihn mit einem anderen Compiler zu kompilieren. Sofortiger Kompilierfehler. Das Programm ist nicht portabel.
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 zumindest einen genauen Dateinamen und eine Zeilennummer für das Problem. Trotzdem haben Sie kein funktionierendes Programm.
andere als irgendeine Form von assert('eax' != 'ebx'); //if this fails you can't compile the code because... Gibt es etwas, was der ursprüngliche Autor tun könnte, um andere Compilerfehler zu verhindern, ohne das Konstrukt vollständig zu ersetzen>
– Dan spielt bei Feuerschein
8. August 2017 um 14:01 Uhr
Zwei Case-Labels mit demselben Wert sind eine Einschränkungsverletzung (6.8.4.2p3: “…no two of the case constant expressions in the same switch statement must have the same value after conversion”), solange der gesamte Code die Werte dieser Konstanten als undurchsichtig behandelt, funktioniert dies garantiert oder schlägt fehl.
– zol
8. August 2017 um 17:33 Uhr
Das Schlimmste ist, dass der arme Kerl, der auf einem anderen Compiler kompiliert, wahrscheinlich keinen sehen wird Kompilierzeit Fehler (Einschalten von Ints ist in Ordnung); stattdessen, Laufzeit Fehler werden auftauchen…
– Tucuxi
9. August 2017 um 12:01 Uhr
Das Codefragment verwendet eine historische Kuriosität namens Zeichenkonstante mit mehreren Zeichenauch bezeichnet als mehrere Zeichen.
'eax' ist eine ganzzahlige Konstante, deren Wert implementierungsdefiniert ist.
Hier ist eine interessante Seite über Multi-Chars und wie sie verwendet werden können, aber nicht sollten:
Wenn man weiter weg in den Rückspiegel blickt, sieht man hier, wie das original C-Handbuch von Dennis Ritchie aus der guten alten Zeit ( https://www.bell-labs.com/usr/dmr/www/cman.pdf ) angegebene Zeichenkonstanten.
2.3.2 Zeichenkonstanten
Eine Zeichenkonstante besteht aus 1 oder 2 Zeichen, eingeschlossen in einfache Anführungszeichen ” ' ”. Innerhalb einer Zeichenkonstante muss einem einfachen Anführungszeichen ein Backslash ” vorangestellt werden\”. Bestimmte nicht grafische Zeichen und ”\” selbst, kann gemäß der folgenden Tabelle maskiert werden:
BS \b
NL \n
CR \r
HT \t
ddd \ddd
\ \\
Die Flucht ”\ddd” besteht aus dem umgekehrten Schrägstrich, gefolgt von 1, 2 oder 3 Oktalziffern, die verwendet werden, um den Wert des gewünschten Zeichens anzugeben. Ein Spezialfall dieser Konstruktion ist ”\0” (ohne gefolgt von einer Ziffer), was ein Nullzeichen anzeigt.
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 niederwertigen Byte und 0 im höherwertigen Byte; eine Zeichenkonstante der Länge 2 hat den Code für das erste Zeichen im niederwertigen 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, was Sie sich über diese merkwürdige Konstruktion merken müssen: Zeichenkonstanten mit mehr als einem Zeichen sind von Natur aus maschinenabhängig und sollten vermieden werden.
14215900cookie-checkDieser Quellcode schaltet einen String in C ein. Wie macht er das?yes
es ist nicht die Saite
'eax'
und es zählt den konstanten ganzzahligen Wert auf– 0___________
7. August 2017 um 15:35 Uhr
Einfache Anführungszeichen, keine doppelten. Eine Zeichenkonstante wird heraufgestuft
int
, also 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,eax
könnte sein0x65
,0x656178
,0x65617800
,0x786165
,0x6165
oder etwas anderes.– Davislor
8. August 2017 um 2:11 Uhr
@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. Genau so lange wie
'eax' != 'ebx'
, natürlich, also scheitert es nur an einem oder zwei Ihrer Beispiele. Obwohl es irgendwo Code geben könnte, der tatsächlich davon ausgeht*(int*)("eax") == 'eax'
und versagt daher bei den meisten Ihrer Beispiele.– Steve Jessop
8. August 2017 um 13:21 Uhr
@SteveJessop Ich bin mit dem, was Sie sagen, nicht einverstanden, aber es besteht die reale Gefahr, dass jemand versuchen könnte, den Code auf einem anderen Compiler zu kompilieren, selbst für dieselbe Architektur, und ein anderes Verhalten erhält. Zum Beispiel,
'eax'
gleich vergleichen könnte'ebx'
oder zu'ax'
und die switch-Anweisung würde nicht wie beabsichtigt funktionieren.– Davislor
8. August 2017 um 21:50 Uhr
All dieses Rätsel wäre schnell zerstreut worden, wenn Sie den Datentyp von reg nachgeschlagen/gezeigt hätten.
– ths
8. August 2017 um 22:43 Uhr