2011-08-16 13 views
6

Possiedo diverse strutture dati, ognuna con un campo di 4 byte.Caso switch C/C++ su array di celle

dal 4 byte sono pari a 1 int sulla mia piattaforma, voglio usarli in case etichette:

switch (* ((int*) &structure->id)) { 
    case (* ((int*) "sqrt")): printf("its a sqrt!"); break; 
    case (* ((int*) "log2")): printf("its a log2!"); break; 
    case (((int) 'A')<<8 + (int) 'B'): printf("works somehow, but unreadable"); break; 
    default: printf("unknown id"); 
} 

Questo si traduce in un errore di compilazione, dicendomi l'espressione caso non ridurre ad un int.

Come posso utilizzare array di caratteri di dimensioni limitate e convertirli in tipi numerici da utilizzare in switch/case?

+1

È per C++ (come nel titolo della domanda) o per C99 (come nei tag)? Non sono sicuro che la risposta sia diversa tra i due, ma vedere due lingue diverse nella domanda senza una chiara motivazione è fonte di confusione. –

+0

corretto il titolo –

+1

Ora ci sono _two_ lingue nel titolo, una delle quali viene ripetuta nei tag. Ne deduciamo che non parli del C++, dopotutto? Perché è ancora nel titolo allora? –

risposta

2

Disclaimer: non usare questo, tranne per scopi divertenti o di apprendimento. Per codice serio, usare espressioni comuni, mai fare affidamento sul comportamento specifico del compilatore nel caso generale; se fatto comunque, le piattaforme incompatibili dovrebbero innescare un errore di compilazione o usare il buon codice generale.


Sembra che la norma consente costanti carattere multi-personaggio come per la grammatica. Non ho ancora verificato se il seguente è davvero legale però.

~/$ cat main.cc 

#include <iostream> 

#ifdef I_AM_CERTAIN_THAT_MY_PLATFORM_SUPPORTS_THIS_CRAP 
int main() { 
    const char *foo = "fooo"; 
    switch ((foo[0]<<24) | (foo[1]<<16) | (foo[2]<<8) | (foo[3]<<0)) { 
    case 'fooo': std::cout << "fooo!\n"; break; 
    default:  std::cout << "bwaah!\n"; break; 
    }; 
} 
#else 
#error oh oh oh 
#endif 

~/$ g++ -Wall -Wextra main.cc && ./a.out 
main.cc:5:10: warning: multi-character character constant 
fooo! 

edit: Oh guarda, direttamente sotto l'estratto di grammatica non v'è 2.13.2letterali carattere, punto 1:

[...] un carattere normale letterale contiene più di un c-char è un letterale multi-carattere. Un valore letterale multicharac- ha tipo int e valore definito dall'implementazione.

Ma nel secondo proiettile:

[...] Il valore di un carattere esteso contenente letterale multipli c-caratteri dipende dall'implementazione.

Quindi attenzione.

0

questo è più C che C++.

unione int_char4 {int_32 x; char [4] y;}

un unione dichiara, definisce i membri di avviarsi sullo stesso indirizzo, fornendo essenzialmente tipi diversi per lo stesso insieme di byte.

int_char4 ic4; ic4.x è un int e ic4.y è un puntatore al primo byte dell'array di caratteri.

poiché, vuoi imparare, l'implementazione spetta a te.

+1

In C99, il tipo intero con segno intero a 32 bit è denominato 'int32_t', non' int_32'. – jwodder

+0

sì, davvero. l'OP indicava, voleva-imparare. :-) Grazie. –

+0

Non penso che risponda alla domanda originale dell'OP. Puoi approfondire questo? – templatetypedef

2

Credo che il problema qui sia che in C, ogni etichetta case in un'istruzione switch deve essere un'espressione costante intera. Dal spec C ISO, § 6.8.4.2/3:

L'espressione di ogni etichetta caso sarà un costante intera espressione [...]

(corsivo mio)

La specifica C definisce quindi un "intero espressione costante" come un'espressione costante dove (§ 6,6/6):

Un'espressione costante intera) deve essere di tipo intero e deve avere solo operandi costanti, costanti di enumerazione, costanti di carattere, dimensioni di espressioni i cui risultati sono costanti intere e costanti mobili che sono gli operandi immediati di calchi. Gli operatori di cast in un'espressione costante intera devono convertire solo i tipi aritmetici solo nei tipi interi, come , eccetto come parte di un operando nella dimensione dell'operatore .

(la mia enfasi di nuovo). Ciò suggerisce che non è possibile digitare un carattere letterale (un puntatore) su un numero intero in un'istruzione case, poiché tale cast non è consentito in un'espressione di costante intera.

Intuitivamente, il motivo potrebbe essere che in alcune implementazioni la posizione effettiva delle stringhe nell'eseguibile generato non è necessariamente specificata fino al collegamento. Di conseguenza, il compilatore potrebbe non essere in grado di emettere un codice molto buono per l'istruzione switch se le etichette dipendono da un'espressione costante che dipende indirettamente dall'indirizzo di quelle stringhe, poiché potrebbe perdere l'opportunità di compilare tabelle di salto, ad esempio.Questo è solo un esempio, ma il linguaggio più rigoroso delle specifiche vi proibisce esplicitamente di fare ciò che avete descritto sopra.

Spero che questo aiuti!

2

Il problema è che i rami case di un switch prevedono un valore costante. In particolare, una costante nota al tempo di compilazione. L'indirizzo delle stringhe non è noto al momento della compilazione - il linker conosce l'indirizzo, ma nemmeno l'indirizzo finale. Penso che l'indirizzo finale, trasferito, sia disponibile solo in fase di esecuzione.

è possibile semplificare il problema per

void f() { 
    int x[*(int*)"x"]; 
} 

Questo produce lo stesso errore, dal momento che l'indirizzo del "x" letterale non è noto al momento della compilazione. Questo è diverso da ad es.

void f() { 
    int x[sizeof("x")]; 
} 

Poiché il compilatore conosce la dimensione del puntatore (4 byte 32bit build).

Ora, come risolvere il problema? Due cose mi vengono in mente:

  1. Non fare il campo id una stringa, ma un intero e quindi utilizzare un elenco di costanti nei vostri case dichiarazioni.

  2. Ho il sospetto che sarà necessario fare un switch come questo in più luoghi, quindi il mio altro suggerimento è: non utilizzare un switch in primo luogo per eseguire codice a seconda del tipo di struttura. Invece, la struttura potrebbe offrire un puntatore a funzione che può essere chiamato per fare la giusta chiamata printf. Al momento della creazione della struttura, il puntatore della funzione viene impostato sulla funzione corretta.

Ecco uno schizzo di codice che illustra la seconda idea:

struct MyStructure { 
    const char *id; 
    void (*printType)(struct MyStructure *, void); 
    void (*doThat)(struct MyStructure *, int arg, int arg); 
    /* ... */ 
}; 

static void printSqrtType(struct MyStructure *) { 
    printf("its a sqrt\n"); 
} 

static void printLog2Type(struct MyStructure *) { 
    printf("its a log2\n"); 
} 

static void printLog2Type(struct MyStructure *) { 
    printf("works somehow, but unreadable\n"); 
} 

/* Initializes the function pointers in the structure depending on the id. */ 
void setupVTable(struct MyStructure *s) { 
    if (!strcmp(s->id, "sqrt")) { 
    s->printType = printSqrtType; 
    } else if (!strcmp(s->id, "log2")) { 
    s->printType = printLog2Type; 
    } else { 
    s->printType = printUnreadableType; 
    } 
} 

Con questo in luogo, il codice originale può fare solo:

void f(struct MyStruct *s) { 
    s->printType(s); 
} 

questo modo, è centralizzare il controllo di tipo in un unico posto invece di ingombrare il codice con un sacco di dichiarazioni switch.

1

Ciò è particolarmente pericoloso a causa dell'allineamento: su molte architetture, int è allineato a 4 byte, ma gli array di caratteri non lo sono. Su sparc, ad esempio, anche se questo codice potesse essere compilato (il che non può essere visto perché l'indirizzo della stringa non è noto fino al momento del collegamento) solleverebbe immediatamente SIGBUS.

4

seguire il metodo esatto impiegato in codifica video di codici FourCC:

Set a FourCC value in C++

#define FOURCC(a,b,c,d) ((uint32) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a))) 

Probabilmente una buona idea di utilizzare i tipi o le macro enumerati per ogni identificatore:

enum { 
    ID_SQRT = FOURCC('s', 'q', 'r', 't'), 
    ID_LOG2 = FOURCC('l', 'o', 'g', '2') 
}; 

int structure_id = FOURCC(structure->id[0], 
          structure->id[1], 
          structure->id[2], 
          structure->id[3]); 
switch (structure_id) { 
case ID_SQRT: ... 
case ID_LOG2: ... 
} 
1

ho appena finito di usare questa macro, simile al caso n. 3 nella domanda o alla risposta in phresnels.

#define CHAR4_TO_INT32(a, b, c, d) ((((int32_t)a)<<24)+ (((int32_t)b)<<16) + (((int32_t)c)<<8)+ (((int32_t)d)<<0)) 

switch (* ((int*) &structure->id)) { 
    case (CHAR4_TO_INT32('S','Q','R','T')): printf("its a sqrt!"); break; 
} 
Problemi correlati