Considere el siguiente código simplificado a continuación. Quiero extraer algunos datos/secuencias binarias de un archivo e imprimirlo en la salida estándar en formato hexadecimal.
Obtuve 3 bytes adicionales 0xFFFFFF . ¿Qué ocurre? ¿De dónde vienen los bytes extra?
producción
in: 2000FFFFFFAF00690033005A00 out: 2000FFFFFFAF00690033005A00programa.c
#include <stdio.h> #include <stdlib.h> int main(int argc, char** argv) { int i; char raw[10] = {0x20,0x00,0xAF,0x00,0x69,0x00,0x33,0x00,0x5A,0x00}; FILE *outfile; char *buf; printf("in:\n\t"); for( i=0; i<10; i++ ) printf("%02X", raw[i]); outfile = fopen("raw_data.bin", "w+b"); fwrite(raw, 1, 10, outfile); buf = (char *) malloc (32 * sizeof(char)); fseek(outfile, 0, SEEK_SET); fread(buf, 1, 10, outfile); printf("\nout:\n\t"); for( i=0; i<10; i++ ) printf("%02X", buf[i]); printf("\n"); fclose(outfile); return 0; }Extensión de signo. Su compilador está implementando char como un signed char . Cuando pasa los caracteres a printf , todos se extienden durante su promoción a int s. Cuando el primer bit es un 0 esto no importa, porque se extiende con 0 s.
0xAF en binario es 10101111 Dado que el primer bit es un 1 , al pasarlo a printf se extiende con todos los 1 en la conversión a int , lo que lo convierte en 11111111111111111111111110101111 , el valor hexadecimal que tiene.
Solución: use unsigned char (en lugar de char ) para evitar que se produzca la extensión de la señal en la llamada
const unsigned char raw[] = {0x20,0x00,0xAF,0x00,0x69,0x00,0x33,0x00,0x5A,0x00}; Todos estos valores en su ejemplo original se están extendiendo, solo que 0xAF es el único con un 1 en el primer bit.
Otro ejemplo más simple del mismo comportamiento ( enlace en vivo ):
signed char c = 0xAF; // probably gives an overflow warning int i = c; // extra 24 bits are all 1 assert( i == 0xFFFFFFAF );Esto se debe a que 0xAF cuando se convierte de un carácter con signo a un entero con signo es negativo (es un signo extendido), y el formato %02X es para argumentos sin signo e imprime el valor convertido como FFFFFFAF .
Los caracteres adicionales aparecen porque printf %x nunca truncará silenciosamente los dígitos de un valor. Los valores que no son negativos también obtienen un signo extendido, pero eso es solo agregar cero bits y el valor cabe en 2 dígitos hexadecimales, por lo que printf %02 puede funcionar con una salida de dos dígitos.
Tenga en cuenta que hay 2 dialectos de C: uno en el que se firma el char simple y otro en el que no está firmado. En el tuyo está firmado. Puede cambiarlo usando una opción, por ejemplo, gcc y clang support -funsigned-char and -fsigned-char .
printf() es una función variable y sus argumentos adicionales (correspondientes a ... parte de su prototipo) están sujetos a las promociones de argumento predeterminadas , por lo que char se promociona a int .
Como su char ha firmado 1 , la representación del complemento de dos, el bit más significativo se establece en uno para el elemento 0xAF . Durante la promoción, se propaga el bit firmado, lo que da como resultado 0xFFFFFFAF de tipo int , como presumiblemente sizeof(int) = 4 en su implementación.
Por cierto, está invocando un comportamiento indefinido , ya que el especificador de formato %X debe usarse para objetos de tipo unsigned int o al menos para int con MSB que no está configurado (esta es una práctica común y ampliamente aceptada).
Como se sugirió, puede considerar el uso de un tipo de unsigned char inequívoco.
1) La ejecución puede elegir entre la representación firmada y no firmada de char . Es bastante común que char esté firmado, pero no puedes darlo por sentado para todos los demás compiladores del planeta. Algunos de ellos pueden permitir elegir entre estos dos modos, como se menciona en la respuesta de Jens .