¿Cómo convertir una cadena hexadecimal en una matriz de caracteres sin firmar?

9 minutos de lectura

avatar de usuario
Gbps

Por ejemplo, tengo un cstring "E8 48 D8 FF FF 8B 0D" (incluidos los espacios) que debe convertirse en la matriz de caracteres sin firmar equivalente {0xE8,0x48,0xD8,0xFF,0xFF,0x8B,0x0D}. ¿Cuál es una manera eficiente de hacer esto? ¡Gracias!

EDITAR: No puedo usar la biblioteca estándar … así que considere esto como una pregunta C. ¡Lo siento!

Esto responde a la original pregunta, que pedía una solución C++.

Puedes usar un istringstream con el hex manipulador:

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);
}

Tenga en cuenta que c debe ser un int (o longo algún otro tipo de entero), no un char; si es un char (o unsigned char), lo malo >> se llamará a la sobrecarga y se extraerán los caracteres individuales de la cadena, no las cadenas enteras hexadecimales.

Comprobación de errores adicional para garantizar que el valor extraído se ajuste a un char seria una buena idea

  • Debido a que no puedo dar dos respuestas correctas, seguí adelante y voté a favor de esta, ¡ya que definitivamente es una gran solución para los usuarios de C++!

    – Gbps

    11 de julio de 2010 a las 0:50

Nunca me convencerás de que esta operación es un cuello de botella en el rendimiento. La forma eficiente es hacer un buen uso de su tiempo utilizando la biblioteca C estándar:

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;
}

Compilado y probado. Funciona en tu ejemplo.

  • Elegí esto como la respuesta porque simplemente proporcionó un ejemplo de trabajo. ¡Gracias!

    – Gbps

    11 de julio de 2010 a las 0:51

  • OTOH, desbordamiento de búfer en “ABCDEF 1 2 3 4 5 6 7 8 9”.

    – Ben Voigt

    11 de julio de 2010 a las 1:08

  • Mucho más simple: for (i=0; i<max && isxdigit(*s); i++) a[i]=strtol(s, &s, 16); El punto es que su gethex La función es totalmente redundante. strtol omite los espacios en blanco iniciales. Si desea ser más estricto acerca de no aceptar cadenas que no coincidan con el patrón, puede usar sscanf para controlar el ancho del campo y medir las longitudes de coincidencia.

    – R.. GitHub DEJA DE AYUDAR A ICE

    11 de julio de 2010 a las 4:54


  • @R: gran punto sobre strtoul — No leí la página de manual con suficiente cuidado. Siéntete libre de editar.

    —Norman Ramsey

    11 de julio de 2010 a las 5:46

  • Esto no puede funcionar correctamente solo si hay espacios en cada dos dígitos. En mi opinión, esto hace que este enfoque sea una mierda.

    – Marek R.

    25 de noviembre de 2016 a las 14:51

avatar de usuario
ben voigt

  • Iterar a través de todos los caracteres.
    • Si tiene un dígito hexadecimal, el número es (ch >= 'A')? (ch - 'A' + 10): (ch - '0').
      • Desplace su acumulador a la izquierda en cuatro bits y agregue (o OR) en el nuevo dígito.
    • Si tiene un espacio y el carácter anterior no era un espacio, agregue el valor de su acumulador actual a la matriz y restablezca el acumulador a cero.

  • +1: Esta es probablemente la forma más directa y sencilla de hacerlo.

    –James McNellis

    10 de julio de 2010 a las 23:22

  • Eso es básicamente lo que hice, excepto por usar el interruptor en lugar de la prueba ternaria. Dependiendo de la arquitectura del compilador y del procesador, uno u otro puede ser más rápido. Pero también debe probar que cada carácter esté en el rango 0-9A-F, y hace que la prueba sea la misma dos veces.

    – kriss

    10 de julio de 2010 a las 23:42


  • @kriss: Todo está en las suposiciones. Usted asume que debe haber exactamente dos dígitos hexadecimales y un espacio entre cada valor, el mío permite la omisión de un cero inicial o múltiples espacios, pero asume que no hay otras clases de caracteres en la cadena. Si no puede asumir eso, probablemente elegiría hacer la validación por separado, probando if (s[strspn(s, " 0123456789ABCDEF")]) /* error */; Claro, es otra pasada en la cuerda, pero mucho más limpia. O evite el segundo paso sobre la cuerda usando isspace y isxdigit en cada carácter, que utiliza una tabla de búsqueda para la velocidad.

    – Ben Voigt

    11 de julio de 2010 a las 0:19


  • Hacer bucles alrededor de los interruptores no es realmente un problema, realmente no lo tomo como una diferencia. Elegí suponer que había exactamente dos caracteres hexadecimales en la entrada, porque si permite más que eso, también debe verificar el rango de valores. ¿Y qué hay de permitir números negativos, tendríamos que administrar el cambio de signo, etc. es una especie de tabla de búsqueda… (y otro método de conversión rápida sería realmente usar una implementada como una matriz).

    – kriss

    11 de julio de 2010 a las 0:40

  • El problema especificaba que todas las entradas no estaban firmadas. El problema no especificaba que siempre habría ceros de relleno hasta exactamente dos dígitos (por ejemplo, todos estos caben en un char: 0xA, 0x0A, 0x000A) o solo un espacio, aunque estas suposiciones eran ciertas en la entrada de muestra.

    – Ben Voigt

    11 de julio de 2010 a las 1:23

Si conoce la longitud de la cadena que se analizará de antemano (por ejemplo, está leyendo algo de /proc), puede usar sscanf con el modificador de tipo ‘hh’, que especifica que la próxima conversión es una de diouxX y el puntero para almacenarlo será un carácter firmado o un carácter sin firmar.

// 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]);
}

Producción:

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

utilice la función “antigua” 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;

avatar de usuario
bjg

Para una implementación de C puro, creo que puede persuadir sscanf(3) para hacer lo que tú qué. Creo que esto debería ser portátil (incluida la coerción de tipo ligeramente dudosa para apaciguar al compilador) siempre que su cadena de entrada solo contenga valores hexadecimales de dos caracteres.

#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;
}

La vieja forma C, hazlo a mano 😉 (hay muchas formas más cortas, pero no estoy jugando al golf, voy por el tiempo de ejecución).

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");

Ahora, otro que permite cualquier número de dígitos entre números, cualquier número de espacios para separarlos, incluidos los espacios iniciales o finales (especificaciones de Ben):

#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, en realidad no está ofuscado… pero bueno, parece que lo está.

  • ¿Se nos permite decir ‘¡Puaj!’? (Aunque solo sea porque el código “arrojará una excepción” en el último ciclo, porque solo hay 6 espacios en la cadena, no 7 como requiere el código).

    –Jonathan Leffler

    10 de julio de 2010 a las 23:43


  • @Jonathan: ya no… También podría haber agregado un espacio para ingresar. El viejo debate entre separadores y terminadores.

    – kriss

    11 de julio de 2010 a las 0:43


  • Tu pequeña solución no ayuda… *p != ' ' en la terminación NUL y no importa con qué lógica, o eso.

    – Ben Voigt

    11 de julio de 2010 a las 1:05

  • Opps, me equivoqué de nuevo. Debería gustarte más la nueva solución 🙂

    – kriss

    11 de julio de 2010 a las 1:15

  • El control de validez todavía es escamoso.

    – Ben Voigt

    11 de julio de 2010 a las 1:24

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad