Come trasformare una stringa esadecimale in un unsigned char array?

Per esempio, ho un oggetto cstring "E8 48 D8 FF FF 8B 0D" (spazi inclusi), che deve essere convertito nell’equivalente unsigned char array {0xE8,0x48,0xD8,0xFF,0xFF,0x8B,0x0D}. Che cosa è un modo efficace per fare questo? Grazie!

EDIT: non posso usare la libreria std… in modo da considerare questo un C domanda. Mi dispiace!

InformationsquelleAutor Gbps | 2010-07-10

 

7 Replies
  1. 11

    Potrai mai convincermi che questa operazione è un collo di bottiglia delle prestazioni.
    Il modo più efficiente è quello di fare buon uso del vostro tempo utilizzando la libreria C standard:

    static unsigned char gethex(const char *s, char **endptr) {
      assert(s);
      while (isspace(*s)) s++;
      assert(*s);
      return strtoul(s, endptr, 16);
    }
    
    unsigned char *convert(const char *s, int *length) {
      unsigned char *answer = malloc((strlen(s) + 1) / 3);
      unsigned char *p;
      for (p = answer; *s; p++)
        *p = gethex(s, (char **)&s);
      *length = p - answer;
      return answer;
    }

    Compilato e testato. Funziona sul tuo esempio.

    • Ho scelto questa come risposta perché semplicemente fornito un esempio. Grazie!
    • OTOH, buffer overflow su “A B C D E F 1 2 3 4 5 6 7 8 9”.
    • Molto più semplice: for (i=0; i<max && isxdigit(*s); i++) a[i]=strtol(s, &s, 16); Il punto di essere, il tuo gethex funzione è completamente ridondante. strtol salta leader spazio stesso. Se vuoi essere più rigida di non accettare le stringhe che non corrispondono al modello, è possibile utilizzare sscanf per controllare la larghezza del campo e misurare la lunghezza di una corrispondenza.
    • grande punto di strtoul—non ho letto la pagina di man con sufficiente attenzione. Sentitevi liberi di modificare.
    • Questo cant funziona correttamente solo se gli spazi sono presenti in ogni numero a due cifre. IMO questo rende questo approccio di merda.
  2. 28

    Questo risponde alla originale domanda, che chiedeva un C++ soluzione.

    È possibile utilizzare un istringstream con il hex manipolatore:

    std::string hex_chars("E8 48 D8 FF FF 8B 0D");
    
    std::istringstream hex_chars_stream(hex_chars);
    std::vector<unsigned char> bytes;
    
    unsigned int c;
    while (hex_chars_stream >> std::hex >> c)
    {
        bytes.push_back(c);
    }

    Nota che c deve essere un int (o long, o qualche altro tipo integer), non char; se è un char (o unsigned char), sbagliato >> sovraccarico sarà chiamato e singoli caratteri verranno estratti dalla stringa, non intero esadecimale stringhe.

    Ulteriori errori di controllo per garantire che il valore estratto si inserisce all’interno di un char sarebbe una buona idea.

    • +1 e cancellare il mio equivalente (ma non così buono) risposta.
    • Perché io non posso dare due risposte corrette, sono andato avanti e con voto positivo, in quanto questo è sicuramente un ottima soluzione per C++ utenti!
  3. 6
    • Scorrere tutti i caratteri.
      • Se si dispone di una cifra esadecimale, il numero è (ch >= 'A')? (ch - 'A' + 10): (ch - '0').
        • A sinistra spostare il vostro accumulatore da quattro bit e aggiungere (o O) la nuova cifra.
      • Se si dispone di uno spazio, e il carattere precedente non era uno spazio, quindi aggiungere il vostro attuale il valore dell’accumulatore di matrice e di azzerare l’accumulatore di nuovo a zero.
    • +1: Questo è probabilmente il modo più diretto e semplice per farlo.
    • Questo è fondamentalmente ciò che ho fatto, tranne che per l’utilizzo di switch invece di ternario di prova. A seconda del compilatore e l’architettura del processore o l’uno o l’altro può essere più veloce. Ma si dovrebbe anche esaminare ogni personaggio è nel range 0-9A-F, e rende test la stessa cosa due volte.
    • Tutti i presupposti. Dai per scontato che ci deve essere esattamente due cifre esadecimali, e di uno spazio tra ogni valore, il mio permette l’omissione di uno zero o più spazi, ma si presuppone che non ci sono altre classi di caratteri nella stringa. Se si può supporre che, probabilmente sarei scegliere la convalida separatamente, da test if (s[strspn(s, " 0123456789ABCDEF")]) /* error */; Certo, è un altro passaggio sulla corda, ma in modo molto più pulito. O evitare il secondo passaggio sul stringa utilizzando isspace e isxdigit su ogni personaggio, che utilizza una tabella di ricerca per la velocità.
    • Loop intorno switch non è davvero un problema, non mi prende come una differenza. Ho scelto di assumere c’era esattamente due hex char in ingresso, perché se si permette a più di che si dovrebbe anche controllare la gamma di valori. E per quanto riguarda permettendo negativer numeri, ci sarebbe da gestire segno, etc. l’opzione una sorta di tabella di ricerca… (e un altro di conversione veloce metodo sarebbe davvero utilizzare uno implementato come un array).
    • Il problema specificato che tutti gli ingressi sono stati firmati. Il problema non è stato specificato che ci sarebbe sempre zeri imbottitura a due cifre (ad esempio, tutti questi in forma in un char: 0xA, 0x0A, 0x000A) o di un solo spazio, anche se queste ipotesi sono vere sull’ingresso del campione.
    • Si dovrebbe usare isxdigit prima. O v. R il commento di sopra.

  4. 2

    Se si conosce la lunghezza della stringa da analizzare in anticipo (ad esempio la lettura di qualcosa da /proc) è possibile utilizzare sscanf con il ‘hh’ tipo di modificatore, che specifica che la successiva conversione è uno dei diouxX e il puntatore store sarà sia signed o unsigned char char.

    //example: ipv6 address as seen in /proc/net/if_inet6:
    char myString[] = "fe80000000000000020c29fffe01bafb";
    unsigned char addressBytes[16];
    sscanf(myString, "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx
    %02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", &addressBytes[0],
    &addressBytes[1], &addressBytes[2], &addressBytes[3], &addressBytes[4], 
    &addressBytes[5], &addressBytes[6], &addressBytes[7], &addressBytes[8], 
    &addressBytes[9], &addressBytes[10], addressBytes[11],&addressBytes[12],
    &addressBytes[13], &addressBytes[14], &addressBytes[15]);
    
    int i;
    for (i = 0; i < 16; i++){
        printf("addressBytes[%d] = %02x\n", i, addressBytes[i]);
    }

    Di uscita:

    addressBytes[0] = fe
    addressBytes[1] = 80
    addressBytes[2] = 00
    addressBytes[3] = 00
    addressBytes[4] = 00
    addressBytes[5] = 00
    addressBytes[6] = 00
    addressBytes[7] = 00
    addressBytes[8] = 02
    addressBytes[9] = 0c
    addressBytes[10] = 29
    addressBytes[11] = ff
    addressBytes[12] = fe
    addressBytes[13] = 01
    addressBytes[14] = ba
    addressBytes[15] = fb
  5. 2

    utilizzare il “vecchio” funzione sscanf ():

    string s_hex = "E8 48 D8 FF FF 8B 0D"; //source string
    char *a_Char = new char( s_hex.length()/3 +1 ); //output char array
    
    for( unsigned i = 0, uchr ; i < s_hex.length() ; i += 3 ) {
        sscanf( s_hex.c_str()+ i, "%2x", &uchr ); //conversion
        a_Char[i/3] = uchr; //save as char
      }
    delete a_Char;
  6. 0

    Per puro C attuazione penso che si può convincere sscanf(3) di fare ciò che si desidera. Credo che questo dovrebbe essere portatile (incluso il un po ‘ dodgy tipo di coercizione per placare il compilatore) se la stringa di input è sempre e solo andando a contenere due caratteri hex valori.

    #include <stdio.h>
    #include <stdlib.h>
    
    
    char hex[] = "E8 48 D8 FF FF 8B 0D";
    char *p;
    int cnt = (strlen(hex) + 1) / 3; //Whether or not there's a trailing space
    unsigned char *result = (unsigned char *)malloc(cnt), *r;
    unsigned char c;
    
    for (p = hex, r = result; *p; p += 3) {
        if (sscanf(p, "%02X", (unsigned int *)&c) != 1) {
            break; //Didn't parse as expected
        }
        *r++ = c;
    }
    • Dichiarare c come unsigned int, altrimenti si potrebbe sovrascrivere altre variabili locali (o, peggio ancora, il vostro indirizzo di ritorno).
    • Ma in genere scanf sta andando a prendere più tempo per capire il codice del formato di tutta la mia risposta, e la domanda ha chiesto per un efficacia modo.
    • Voigt. Sì, ma non efficace run-time o programmatore a tempo? ‘-) Comunque grazie per la precisazione che mi hanno fatto c un insigned int e costretto in result array.
    • UB. Dal momento che a fine prevista p punti di un byte DOPO aver terminato zero.
    • Buona pesca. Ero chiaramente in due menti a scrivere questo (6 anni fa), avendo dichiarato un cnt variabile e quindi non avendo usato
  7. -1

    La vecchia C fatto a mano 😉 (ci sono molti metodi più rapidi, ma io non sono il golf, io vado per la fase di esecuzione).

    enum { NBBYTES = 7 };
    char res[NBBYTES+1];
    const char * c = "E8 48 D8 FF FF 8B 0D";
    const char * p = c;
    int i = 0;
    
    for (i = 0; i < NBBYTES; i++){
        switch (*p){
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
          res[i] = *p - '0';
        break;
        case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
          res[i] = *p - 'A' + 10;
        break;
       default:
         //parse error, throw exception
         ;
       }
       p++;
       switch (*p){
       case '0': case '1': case '2': case '3': case '4':
       case '5': case '6': case '7': case '8': case '9':
          res[i] = res[i]*16 + *p - '0';
       break;
       case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
          res[i] = res[i]*16 + *p - 'A' + 10;
       break;
       default:
          //parse error, throw exception
          ;
       }
       p++;
       if (*p == 0) { continue; }
       if (*p == ' ') { p++; continue; }
       //parse error, throw exception
    }
    
    //let's show the result, C style IO, just cout if you want C++
    for (i = 0 ; i < 7; i++){
       printf("%2.2x ", 0xFF & res[i]);
    }
    printf("\n");

    Ora un altro che consente per qualsiasi numero di cifre tra i numeri, un numero qualsiasi di spazi per separare loro, compresi gli spazi iniziali o finali (Ben specifiche):

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(){
        enum { NBBYTES = 7 };
        char res[NBBYTES];
        const char * c = "E8 48 D8 FF FF 8B 0D";
        const char * p = c;
        int i = -1;
    
        res[i] = 0;
        char ch = ' ';
        while (ch && i < NBBYTES){
           switch (ch){
           case '0': case '1': case '2': case '3': case '4':
           case '5': case '6': case '7': case '8': case '9':
              ch -= '0' + 10 - 'A';
           case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
              ch -= 'A' - 10;
              res[i] = res[i]*16 + ch;
              break;
           case ' ':
             if (*p != ' ') {
                 if (i == NBBYTES-1){
                     printf("parse error, throw exception\n");
                     exit(-1);
                }
                res[++i] = 0;
             }
             break;
           case 0:
             break;
           default:
             printf("parse error, throw exception\n");
             exit(-1);
           }
           ch = *(p++);
        }
        if (i != NBBYTES-1){
            printf("parse error, throw exception\n");
            exit(-1);
        }
    
       for (i = 0 ; i < 7; i++){
          printf("%2.2x ", 0xFF & res[i]);
       }
       printf("\n");
    }

    No, non è davvero offuscato… ma sembra proprio di si.

    • Ci è permesso di dire ” Ick!’? (Se solo perché il codice di ‘gettare l’eccezione’ l’ultimo ciclo, perché ci sono solo 6 spazi nella stringa, non 7 come richiede il codice.)
    • non più… potrei anche aver aggiunto uno spazio di ingresso. Il vecchio separatori vs terminazione del dibattito.
    • il tuo piccolo fix non aiuta… *p != ' ' sulla terminazione NUL e non importa che cosa è logica-o che con.
    • Opps, ho fatto errare di nuovo. Si dovrebbe, come il nuovo fix meglio 🙂
    • Controllo di validità è ancora traballante.
    • essere paziente, non ho inviato alcun cambiamento di sicurezza…

Lascia un commento