Copiar una matriz de caracteres de 4 elementos en un entero en C

Un char es de 1 byte y un entero es de 4 bytes. Quiero copiar byte a byte de un char [4] en un entero. Pensé en diferentes métodos pero estoy obteniendo respuestas diferentes.

char str[4]="abc"; unsigned int a = *(unsigned int*)str; unsigned int b = str[0]<<24 | str[1]<<16 | str[2]<<8 | str[3]; unsigned int c; memcpy(&c, str, 4); printf("%u %u %u\n", a, b, c); 

La salida es 6513249 1633837824 6513249

¿Cuál es la correcta? ¿Qué va mal?

Es un problema de endianidad . Cuando interpretas el char* como un int* el primer byte de la cadena se convierte en el byte menos significativo del entero (porque ejecutaste este código en x86, que es un poco endian), mientras que con la conversión manual el primer byte se convierte en el más significativo .

Para poner esto en imágenes, esta es la matriz de origen:

  abc \0 +------+------+------+------+ | 0x61 | 0x62 | 0x63 | 0x00 | <---- bytes in memory +------+------+------+------+ 

Cuando estos bytes se interpretan como un número entero en una pequeña architecture 0x00636261 , el resultado es 0x00636261 , que es decimal 6513249. Por otra parte, al colocar cada byte manualmente, se obtiene 0x61626300 - decimal 1633837824.

Por supuesto, tratar a un char* como un int* es un comportamiento indefinido, por lo que la diferencia no es importante en la práctica porque realmente no se le permite usar la primera conversión. Sin embargo, hay una forma de lograr el mismo resultado, que se denomina punning de tipo :

 union { char str[4]; unsigned int ui; } u; strcpy(u.str, "abc"); printf("%u\n", u.ui); 

Ninguno de los dos primeros es correcto.

La primera viola las reglas de alias y puede fallar porque la dirección de str no está correctamente alineada para un unsigned int . Para reinterpretar los bytes de una cadena como un unsigned int con el orden de bytes del sistema host, puede copiarlo con memcpy :

 unsigned int a; memcpy(&a, &str, sizeof a); 

(Suponiendo que el tamaño de un unsigned int y el tamaño de str son los mismos).

El segundo puede fallar con un desbordamiento de enteros porque str[0] se promueve a un int , por lo que str[0]<<24 tiene el tipo int , pero el valor requerido por el cambio puede ser mayor que el que se puede representar en un int . Para remediar esto, use:

 unsigned int b = (unsigned int) str[0] << 24 | …; 

Este segundo método interpreta los bytes de str en el orden de big-endian, independientemente del orden de los bytes en un unsigned int en el sistema host.

 unsigned int a = *(unsigned int*)str; 

Esta inicialización no es correcta e invoca un comportamiento indefinido. Viola las reglas de alias de C y potencialmente viola la alineación del procesador.

Usted dijo que desea copiar byte por byte.

Eso significa que la línea unsigned int a = *(unsigned int*)str; No se permite. Sin embargo, lo que estás haciendo es una forma bastante común de leer una matriz como un tipo diferente (como cuando estás leyendo una secuencia desde el disco).

Sólo necesita algunos ajustes:

  char * str ="abc"; int i; unsigned a; char * c = (char * )&a; for(i = 0; i < sizeof(unsigned); i++){ c[i] = str[i]; } printf("%d\n", a); 

Tenga en cuenta que los datos que está leyendo pueden no compartir la misma endianidad que la máquina de la que está leyendo. Esto podría ayudar:

 void changeEndian32(void * data) { uint8_t * cp = (uint8_t *) data; union { uint32_t word; uint8_t bytes[4]; }temp; temp.bytes[0] = cp[3]; temp.bytes[1] = cp[2]; temp.bytes[2] = cp[1]; temp.bytes[3] = cp[0]; *((uint32_t *)data) = temp.word; } 

Ambos son correctos de una manera:

  • Su primera solución se copia en orden de bytes nativo (es decir, el orden de bytes que utiliza la CPU) y, por lo tanto, puede dar diferentes resultados según el tipo de CPU.

  • Su segunda solución copia en orden de bytes big endian (es decir, el byte más significativo en la dirección más baja) sin importar lo que use la CPU. Dará el mismo valor en todos los tipos de CPU.

Lo que es correcto depende de cómo deben interpretarse los datos originales (conjunto de caracteres).
Por ejemplo, el código Java (archivos de clase) siempre usa el orden de bytes big endian (no importa lo que esté usando la CPU). Entonces, si desea leer int s desde un archivo de clase Java, debe utilizar la segunda forma. En otros casos, es posible que desee utilizar la forma dependiente de la CPU (creo que Matlab escribe int s en orden de bytes nativo en archivos, consulte esta pregunta ).

Si usa el comstackdor CVI (National Instruments), puede usar la función Explorar para hacer esto:

unsigned int a;

Para big endian: Scan (str, “% 1i [b4uzi1o3210]>% i”, & a);

Para little endian: Scan (str, “% 1i [b4uzi1o0123]>% i”, & a);

El modificador o especifica el orden de bytes. i dentro de los corchetes indica dónde comenzar en la matriz str.