Questo codice sorgente attiva una stringa in C. Come funziona?


106

Sto leggendo un codice di emulatore e ho contrastato qualcosa di veramente strano:

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

Com'è possibile? Pensavo potessi solo switchsui tipi integrali. C'è qualche trucco macro in corso?


29
non è la stringa 'eax'ed enumera un valore intero costante
P__J__

12
Virgolette singole, non doppie. Una costante di carattere viene promossa a int, quindi è legale. Tuttavia, il valore di una costante multi-carattere è definito dall'implementazione, quindi il codice potrebbe non funzionare come previsto su un altro compilatore. Ad esempio, eaxpotrebbe essere 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, o qualcos'altro.
Davislor

2
@Davislor: dato il nome della variabile "reg", e il fatto che eax è un registro x86, immagino che il comportamento definito dall'implementazione fosse inteso per essere OK, perché è lo stesso ovunque sia usato nel codice. Fintanto che 'eax' != 'ebx', ovviamente, fallisce solo uno o due dei tuoi esempi. Anche se potrebbe esserci del codice da qualche parte che in effetti presuppone *(int*)("eax") == 'eax'e quindi non supera la maggior parte dei tuoi esempi.
Steve Jessop

2
@SteveJessop Non sono in disaccordo con quello che dici, ma c'è il pericolo reale che qualcuno possa provare a compilare il codice su un compilatore diverso, anche per la stessa architettura, e ottenere un comportamento diverso. Ad esempio, 'eax'potrebbe confrontare uguale a 'ebx'o a 'ax'e l'istruzione switch non funzionerebbe come previsto.
Davislor

1
Tutto quel mistero sarebbe stato dissipato rapidamente se tu avessi cercato / mostrato il tipo di dati di reg.
giovedì

Risposte:


146

(Solo tu puoi rispondere alla parte "trucco macro" - a meno che non incolli più codice. Ma qui non c'è molto su cui lavorare per le macro - formalmente non ti è permesso ridefinire le parole chiave ; il comportamento nel farlo è indefinito.)

Per ottenere la leggibilità del programma, lo sviluppatore arguto sta sfruttando il comportamento definito dall'implementazione . non'eax' è una stringa, ma una costante multi-carattere . Nota molto attentamente i singoli caratteri di citazione in giro . Molto probabilmente ti sta dando un nel tuo caso che è unico per quella combinazione di caratteri. (Abbastanza spesso ogni carattere occupa 8 bit in 32 bit ). E tutti sanno che puoi farlo su un !eaxintintswitchint

Infine, un riferimento standard:

Lo standard C99 dice:

6.4.4.4p10: "Il valore di una costante di carattere intero contenente più di un carattere (ad esempio, 'ab'), o contenente un carattere o una sequenza di escape che non si associa a un carattere di esecuzione a byte singolo, è definito dall'implementazione. "


55
Nel caso qualcuno lo vedesse e andasse nel panico, "definito dall'implementazione" è richiesto per funzionare e per essere documentato dal compilatore in modo appropriato (lo standard non richiede che il comportamento sia intuitivo o che la documentazione sia buona, ma ...). Questo è "sicuro" da usare per un programmatore che comprende appieno ciò che sta scrivendo, al contrario di "non definito".
Leushenko

7
@Justin Anche se potrebbe, sarebbe abbastanza perverso. Se non fa ciò che la risposta suggerisce è molto probabile, la prossima possibilità è probabilmente che usi solo il primo carattere e ignori il resto.
Barmar

5
@ZanLynx Non sono positivo, ma credo che la funzione sia antecedente a Unicode e ad altri standard MBCS. I "numeri magici" che assomigliano a testo nei dump della memoria e ID di blocchi di formato di file in stile RIFF sono state le prime applicazioni di cui sono a conoscenza.
Russell Borogove

16
@ jpmc26 Questo non è un comportamento indefinito, è definito dall'implementazione. Quindi, a meno che la documentazione del compilatore non menzioni i demoni, il tuo naso è al sicuro.
Barmar

7
@ZanLynx: temo che l'intento originale preceda Unicode, UTF-8 e qualsiasi codifica di caratteri multibyte di quasi 20 anni. le costanti multi-carattere erano solo un modo pratico per esprimere numeri interi che rappresentano gruppi di 2, 3 o 4 byte (a seconda delle dimensioni di byte e int). Le incongruenze tra le implementazioni e le architetture hanno portato il comitato a dichiararlo come definito dall'implementazione , il che significa che non esiste un modo portabile per calcolare il valore di 'ab'from 'a'e 'b'.
chqrlie

45

Secondo lo standard C (6.8.4.2 L'istruzione switch)

3 L'espressione di ciascuna etichetta del caso deve essere un'espressione costante intera ...

e (6.6 Espressioni costanti)

6 Un'espressione costante intera deve essere di tipo intero e deve avere solo operandi che sono costanti intere, costanti di enumerazione, costanti carattere , dimensione di espressioni i cui risultati sono costanti intere e costanti mobili che sono gli operandi immediati di cast. Gli operatori di cast in un'espressione di costante intera convertono solo i tipi aritmetici in tipi interi, tranne come parte di un operando nell'operatore sizeof.

Ora che cos'è 'eax'?

Lo standard C (6.4.4.4 Costanti dei caratteri)

2 Una costante di caratteri interi è una sequenza di uno o più caratteri multibyte racchiusi tra virgolette singole , come in 'x' ...

Quindi 'eax'è un carattere intero costante secondo il paragrafo 10 della stessa sezione

  1. ... Il valore di una costante di caratteri interi contenente più di un carattere (ad esempio, 'ab'), o contenente un carattere o una sequenza di escape che non si associa a un carattere di esecuzione a byte singolo, è definito dall'implementazione.

Quindi, secondo la prima citazione citata, può essere un operando di un'espressione costante intera che può essere utilizzata come etichetta del caso.

Prestare attenzione a che una costante di carattere (racchiusa tra virgolette singole) ha un tipo inte non è la stessa di una stringa letterale (una sequenza di caratteri racchiusa tra virgolette doppie) che ha un tipo di matrice di caratteri.


12

Come altri hanno già detto, questa è una intcostante e il suo valore effettivo è definito dall'implementazione.

Presumo che il resto del codice assomigli a qualcosa

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

Puoi essere certo che "eax" nella prima parte ha lo stesso valore di "eax" nella seconda, quindi funziona tutto, giusto? ... sbagliato.

In un commento @Davislor elenca alcuni possibili valori per 'eax':

... 0x65, 0x656178, 0x65617800, 0x786165, 0x6165, o qualcos'altro

Notate il primo valore potenziale? Questo è solo 'e', ignorando gli altri due personaggi. Il problema è il programma probabilmente usa 'eax', 'ebx'e così via. Se tutte queste costanti hanno lo stesso valore con 'e'cui finisci

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

Non sembra troppo bello, vero?

La parte buona di "definizione dell'implementazione" è che il programmatore può controllare la documentazione del proprio compilatore e vedere se fa qualcosa di sensato con queste costanti. Se lo fa, a casa gratis.

La parte negativa è che qualche altro poveretto può prendere il codice e provare a compilarlo usando un altro compilatore. Errore di compilazione istantaneo. Il programma non è portatile.

Come ha sottolineato @zwol nei commenti, la situazione non è così grave come pensavo, nel brutto caso il codice non si compila. Questo ti darà almeno un nome di file esatto e un numero di riga per il problema. Tuttavia, non avrai un programma funzionante.


1
oltre a qualche forma assert('eax' != 'ebx'); //if this fails you can't compile the code because...c'è qualcosa che l'autore originale potrebbe fare per prevenire altri errori del compilatore senza sostituire completamente il costrutto?
Dan Is Fiddling By Firelight

6
Due etichette case con lo stesso valore sono una violazione del vincolo (6.8.4.2p3: "... nessuna delle due espressioni di costanti case nella stessa istruzione switch deve avere lo stesso valore dopo la conversione") quindi, fintanto che tutto il codice tratta i valori di queste costanti come opachi, è garantito che funzioni o che non venga compilato.
zwol

La parte peggiore è che il poveretto che compila su un altro compilatore probabilmente non vedrà alcun errore in fase di compilazione (accendere ints va bene); invece, si verificheranno errori di
runtime

1

Il frammento di codice utilizza una stranezza storica chiamata costante di caratteri multi-carattere , nota anche come multi-chars .

'eax' è una costante intera il cui valore è definito dall'implementazione.

Ecco una pagina interessante sui multi-caratteri e su come possono essere utilizzati ma non dovrebbero:

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


Guardando indietro nello specchietto retrovisore, ecco come il manuale originale in C di Dennis Ritchie dei bei vecchi tempi ( https://www.bell-labs.com/usr/dmr/www/cman.pdf ) specificava le costanti dei caratteri .

2.3.2 Costanti dei caratteri

Una costante di carattere è composta da 1 o 2 caratteri racchiusi tra virgolette singole '' '''. All'interno di una costante di carattere una singola virgoletta deve essere preceduta da una barra rovesciata '' \''. Alcuni caratteri non grafici e lo stesso '' \'' possono essere sottoposti a escape in base alla seguente tabella:

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

L'escape '' \ddd'' consiste nella barra rovesciata seguita da 1, 2 o 3 cifre ottali che vengono prese per specificare il valore del carattere desiderato. Un caso speciale di questa costruzione è '' \0'' (non seguito da una cifra) che indica un carattere nullo.

Le costanti dei caratteri si comportano esattamente come interi (non, in particolare, come oggetti di tipo carattere). In conformità con la struttura di indirizzamento del PDP-11, una costante di carattere di lunghezza 1 ha il codice per il carattere dato nel byte di ordine inferiore e 0 nel byte di ordine superiore; una costante di carattere di lunghezza 2 ha il codice per il primo carattere nel byte basso e quello per il secondo carattere nel byte di ordine superiore. Le costanti dei caratteri con più di un carattere sono intrinsecamente dipendenti dalla macchina e dovrebbero essere evitate.

L'ultima frase è tutto ciò che devi ricordare su questa curiosa costruzione: le costanti dei caratteri con più di un carattere sono intrinsecamente dipendenti dalla macchina e dovrebbero essere evitate.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.