2012-03-02 7 views
9

Come creo una macro C per ottenere il valore intero di una stringa? Il caso d'uso specifico sta seguendo da una domanda here. Voglio cambiare il codice come questo:Utilizzo di C Preprocessing per ottenere il valore intero di una stringa

enum insn { 
    sysenter = (uint64_t)'r' << 56 | (uint64_t)'e' << 48 | 
       (uint64_t)'t' << 40 | (uint64_t)'n' << 32 | 
       (uint64_t)'e' << 24 | (uint64_t)'s' << 16 | 
       (uint64_t)'y' << 8 | (uint64_t)'s', 
    mov = (uint64_t)'v' << 16 | (uint64_t)'o' << 8 | 
      (uint64_t)'m' 
}; 

A tal:

enum insn { 
    sysenter = INSN_TO_ENUM("sysenter"), 
    mov  = INSN_TO_ENUM("mov") 
}; 

Dove INSN_TO_ENUM espande allo stesso codice. La performance sarebbe la stessa, ma la leggibilità sarebbe aumentata di molto.

sto ritenendo sospetto che in questa forma potrebbe non essere possibile a causa di un'incapacità C del preprocessore per l'elaborazione delle stringhe, quindi questo sarebbe anche una soluzione unpreferred ma accettabile (argomento macro variabile):

enum insn { 
    sysenter = INSN_TO_ENUM('s','y','s','e','n','t','e','r'), 
    mov  = INSN_TO_ENUM('m','o','v') 
}; 
+0

A questo punto scriverei il mio pre-pre-processore. Cercare di farlo a modo tuo sarà solo dolorosamente rivoltante. È uno script banale da scrivere nel tuo linguaggio di scripting preferito. Supponendo che le tue enumerazioni siano relativamente statiche nel tempo (cioè non le stai modificando giorno dopo giorno) allora penso che sarebbe comodo e leggibile. –

+0

@DavidHeffernan: l'ho considerato, ma come ultima risorsa. Se si può fare come macro, abbiamo il vantaggio della leggibilità oltre a essere in grado di astrarre/nascondere questo particolare particolare di implementazione. IMO i vantaggi superano la possibilità di generare tramite 'pre-pre-elaborazione'. –

+0

Non sono sicuro se è possibile che la macro gestisca gli argomenti delle variabili o semplicemente la accetta per passarla in giro. Vedi la mia risposta per l'argomento 1. – Shahbaz

risposta

4

Ecco una fase di compilazione, soluzione pura C, che hai indicato come accettabile. Potrebbe essere necessario estenderlo per mnemonici più lunghi. Continuerò a pensare a quello desiderato (ad esempio INSN_TO_ENUM("sysenter")).Interessante domanda :)

#include <stdio.h> 

#define head(h, t...) h 
#define tail(h, t...) t 

#define A(n, c...) (((long long) (head(c))) << (n)) | B(n + 8, tail(c)) 
#define B(n, c...) (((long long) (head(c))) << (n)) | C(n + 8, tail(c)) 
#define C(n, c...) (((long long) (head(c))) << (n)) | D(n + 8, tail(c)) 
#define D(n, c...) (((long long) (head(c))) << (n)) | E(n + 8, tail(c)) 
#define E(n, c...) (((long long) (head(c))) << (n)) | F(n + 8, tail(c)) 
#define F(n, c...) (((long long) (head(c))) << (n)) | G(n + 8, tail(c)) 
#define G(n, c...) (((long long) (head(c))) << (n)) | H(n + 8, tail(c)) 
#define H(n, c...) (((long long) (head(c))) << (n)) /* extend here */ 

#define INSN_TO_ENUM(c...) A(0, c, 0, 0, 0, 0, 0, 0, 0) 

enum insn { 
    sysenter = INSN_TO_ENUM('s','y','s','e','n','t','e','r'), 
    mov  = INSN_TO_ENUM('m','o','v') 
}; 

int main() 
{ 
    printf("sysenter = %llx\nmov = %x\n", sysenter, mov); 
    return 0; 
} 
+0

Questo compila e sembra che stia facendo la cosa giusta, ma GCC non riconosce la L in '% Lx' e vuole trattare il num come int a 32 bit (forse perché sto compilando per l'architettura x86). –

+0

Ouch! Colpa mia. '% L' è per' long double', dovrebbe essere '% llx', corretto. Tuttavia 'printf()' era solo per test unitari :) –

+1

Amo lo stack overflow perché ogni giorno imparo qualcosa di nuovo! il 'testa/coda (h, t ...)' è molto bello! Non ho mai saputo che si poteva applicare '...' a uno degli argomenti come quello – Shahbaz

2

MODIFICA: Questa risposta può essere utile, quindi non la sto eliminando, ma non rispondo in modo specifico alla domanda. Converte le stringhe in numeri, ma non può essere inserito in un enumerazione perché non calcola il numero in fase di compilazione.

Bene, dato che i numeri interi sono a 64 bit, si devono preoccupare solo i primi 8 caratteri di qualsiasi stringa. Pertanto, è possibile scrivere la cosa 8 volte, facendo attenzione a non uscire della stringa legato:

#define GET_NTH_BYTE(x, n) (sizeof(x) <= n?0:((uint64_t)x[n] << (n*8))) 
#define INSN_TO_ENUM(x)  GET_NTH_BYTE(x, 0)\ 
          |GET_NTH_BYTE(x, 1)\ 
          |GET_NTH_BYTE(x, 2)\ 
          |GET_NTH_BYTE(x, 3)\ 
          |GET_NTH_BYTE(x, 4)\ 
          |GET_NTH_BYTE(x, 5)\ 
          |GET_NTH_BYTE(x, 6)\ 
          |GET_NTH_BYTE(x, 7) 

Ciò che fa è sostanzialmente quella di verificare in ogni byte se è nel limite della stringa e se lo è, quindi ti dà il byte corrispondente.

Nota: che funziona solo su stringhe letterali.

Se si vuole essere in grado di convertire qualsiasi stringa, si può dare la lunghezza della stringa con esso:

#define GET_NTH_BYTE(x, n, l) (l < n?0:((uint64_t)x[n] << (n*8))) 
#define INSN_TO_ENUM(x, l)  GET_NTH_BYTE(x, 0, l)\ 
           |GET_NTH_BYTE(x, 1, l)\ 
           |GET_NTH_BYTE(x, 2, l)\ 
           |GET_NTH_BYTE(x, 3, l)\ 
           |GET_NTH_BYTE(x, 4, l)\ 
           |GET_NTH_BYTE(x, 5, l)\ 
           |GET_NTH_BYTE(x, 6, l)\ 
           |GET_NTH_BYTE(x, 7, l) 

Così, per esempio:

int length = strlen(your_string); 
int num = INSN_TO_ENUM(your_string, length); 

Infine, c'è un modo per evitare di dare la lunghezza, ma richiede al compilatore di calcolare effettivamente le frasi di INSN_TO_ENUM da sinistra a destra. io non sono sicuro se questo è standard:

static int _nul_seen; 
#define GET_NTH_BYTE(x, n) ((_nul_seen || x[n] == '\0')?(_nul_seen=1)&0:((uint64_t)x[n] << (n*8))) 
#define INSN_TO_ENUM(x)  (_nul_seen=0)| 
           (GET_NTH_BYTE(x, 0)\ 
           |GET_NTH_BYTE(x, 1)\ 
           |GET_NTH_BYTE(x, 2)\ 
           |GET_NTH_BYTE(x, 3)\ 
           |GET_NTH_BYTE(x, 4)\ 
           |GET_NTH_BYTE(x, 5)\ 
           |GET_NTH_BYTE(x, 6)\ 
           |GET_NTH_BYTE(x, 7)) 
+0

Questa non è una costante in fase di compilazione , però - è vero? –

+0

Si potrebbe fare questo lavoro usando C++ 11 'constexpr', che funziona anche con gli array - almeno con gcc-4.6 – hirschhornsalz

+0

Questo non funzionerebbe in un' enum' sfortunatamente perché l'assegnazione condizionale è disponibile solo in fase di esecuzione. –

1

Se è possibile utilizzare C++ 11 su un recente compilatore

constexpr uint64_t insn_to_enum(const char* x) { 
    return *x ? *x + (insn_to_enum(x+1) << 8) : 0; 
} 

enum insn { sysenter = insn_to_enum("sysenter") }; 

funzionerà e calcolare la costante durante la fase di compilazione.

0

Alcuni modelli di magia ricorsiva possono fare il trucco. Non crea alcun codice se le costanti sono conosciute al momento della compilazione.

Potrebbe voler tenere d'occhio i tempi di costruzione se lo si utilizza in rabbia però.

// the main recusrsive template magic. 
template <int N> 
struct CharSHift 
{ 
    static __int64 charShift(char* string) 
    { 
     return string[N-1] | (CharSHift<N-1>::charShift(string)<<8); 
    } 
}; 

// need to provide a specialisation for 0 as this is where we need the recursion to stop 
template <> 
struct CharSHift<0> 
{ 
    static __int64 charShift(char* string) 
    { 
     return 0; 
    } 
}; 

// Template stuff is all a bit hairy too look at. So attempt to improve that with some macro wrapping ! 
#define CT_IFROMS(_string_) CharSHift<sizeof _string_ -1 >::charShift(_string_) 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    __int64 hash0 = CT_IFROMS("abcdefgh"); 

    printf("%08llX \n",hash0); 
    return 0; 
} 
+1

Grazie per la tua risposta, ma questo è un progetto C quindi sono più interessato alle soluzioni C, non al C++. –

Problemi correlati