Impresión de caracteres hexadecimales en C

103

Estoy tratando de leer en una línea de caracteres, luego imprimir el equivalente hexadecimal de los caracteres.

Por ejemplo, si tengo una cadena "0xc0 0xc0 abc123", es decir , donde los primeros 2 caracteres están c0en hexadecimal y los caracteres restantes están abc123en ASCII, entonces debería obtener

c0 c0 61 62 63 31 32 33

Sin embargo, printfusar %xme da

ffffffc0 ffffffc0 61 62 63 31 32 33

¿Cómo obtengo el resultado que quiero sin el "ffffff"? ¿Y por qué solo c0 (y 80) tiene el ffffff, pero no los otros caracteres?

Rayne
fuente
La cadena que coincide con su matriz de bytes sería ..."\xc0\xc0abc123"
burito

Respuestas:

132

Estás viendo el ffffffporque charestá firmado en tu sistema. En C, vararg funciona como printfpromoverá todos los enteros menores que inta int. Dado que chares un entero (entero de 8 bits con signo en su caso), sus caracteres se promocionan a inttravés de la extensión de signo.

Dado que c0y 80tienen un bit inicial de 1 bit (y son negativos como un entero de 8 bits), se están extendiendo con el signo, mientras que los demás en su muestra no.

char    int
c0 -> ffffffc0
80 -> ffffff80
61 -> 00000061

He aquí una solución:

char ch = 0xC0;
printf("%x", ch & 0xff);

Esto enmascarará los bits superiores y mantendrá solo los 8 bits inferiores que desee.

Mística
fuente
15
Mi solución usando una unsigned charconversión a es una instrucción más pequeña en gcc4.6 para x86-64 ...
lvella
1
Quizás pueda ayudar. Este es un comportamiento (técnicamente) indefinido porque el especificador xrequiere un tipo sin firmar, pero ch se promueve a int. El código correcto sería simplemente fundido ch a firmar, o usar un yeso para unsigned char y el especificador: hhx.
2501
1
Si es así printf("%x", 0), no se imprime nada.
Gustavo Meira
No imprime nada porque el mínimo está establecido en 0. Para solucionar esto, intente printf("%.2x", 0);que aumentará los caracteres mínimos dibujados a 2. Para establecer un máximo, anteponga el. con un número. Por ejemplo, puede forzar solo 2 caracteres dibujados haciendoprintf("%2.2x", 0);
user2262111
¿Alguna razón por la que printf("%x", ch & 0xff)debería ser mejor que simplemente usar printf("%02hhX", a)como en la respuesta de @ brutal_lobster ?
maxschlepzig
62

De hecho, existe una conversión de tipo a int. También puede forzar el tipo a char utilizando el especificador% hhx.

printf("%hhX", a);

En la mayoría de los casos, también querrá establecer la longitud mínima para llenar el segundo carácter con ceros:

printf("%02hhX", a);

ISO / IEC 9899: 201x dice:

7 Los modificadores de longitud y sus significados son: hh Especifica que el siguiente especificador de conversión d, i, o, u, x o X se aplica a un argumento de carácter con signo o sin signo (el argumento se habrá promocionado de acuerdo con las promociones de enteros, pero su valor se convertirá a un carácter firmado o un carácter sin firmar antes de imprimir); o que un siguiente

brutal_lobster
fuente
30

Puede crear un carácter sin firmar:

unsigned char c = 0xc5;

Imprimiéndolo dará C5y no ffffffc5.

Solo los caracteres mayores de 127 se imprimen con el ffffffporque son negativos (el carácter está firmado).

O puede emitir charmientras imprime:

char c = 0xc5; 
printf("%x", (unsigned char)c);
Hicham
fuente
3
+1 a la mejor respuesta real, escribiendo explícitamente lo más cerca posible de la declaración de datos (pero no más cerca).
Bob Stein
13

Probablemente esté almacenando el valor 0xc0 en una charvariable, lo que probablemente sea un tipo con signo, y su valor sea negativo (conjunto de bits más significativo). Luego, al imprimir, se convierte inty para mantener la equivalencia semántica, el compilador rellena los bytes adicionales con 0xff, por lo que el negativo inttendrá el mismo valor numérico que el negativo char. Para solucionar este problema, simplemente envíe a unsigned charal imprimir:

printf("%x", (unsigned char)variable);
lvella
fuente
13

Puede usar hhpara saber printfque el argumento es un carácter sin firmar. Úselo 0para obtener relleno de cero y 2para establecer el ancho en 2. xo Xpara caracteres hexadecimales en minúsculas / mayúsculas.

uint8_t a = 0x0a;
printf("%02hhX", a); // Prints "0A"
printf("0x%02hhx", a); // Prints "0x0a"

Editar : Si los lectores están preocupados por la afirmación de 2501 de que de alguna manera no son los especificadores de formato "correctos", sugiero que lean el printfenlace nuevamente. Específicamente:

Aunque% c espera un argumento int, es seguro pasar un char debido a la promoción de enteros que tiene lugar cuando se llama a una función variada.

Las especificaciones de conversión correctas para los tipos de caracteres de ancho fijo (int8_t, etc.) se definen en el encabezado <cinttypes>(C ++) o <inttypes.h>(C) (aunque PRIdMAX, PRIuMAX, etc. es sinónimo de% jd,% ju, etc.) .

En cuanto a su punto sobre firmado vs no firmado, en este caso no importa ya que los valores siempre deben ser positivos y encajar fácilmente en un int con signo. De todos modos, no hay un especificador de formato hexadecimal firmado.

Edición 2 : ( edición "cuándo-admitir-que-está-equivocado"):

Si lee el estándar C11 actual en la página 311 (329 del PDF), encontrará:

hh: Especifica que un siguiente d, i, o, u, x, o Xespecificador de conversión se aplica a una signed charo unsigned charargumento (el argumento se habrá promovido de acuerdo con las promociones de enteros, pero su valor se convierte en signed charo unsigned charantes de la impresión); o que un nespecificador de conversión siguiente se aplica a un puntero a un signed charargumento.

Timmmm
fuente
Los especificadores no son correctos para el tipo uint8_t. Los tipos de ancho fijo utilizan especificadores de impresión especiales. Ver:inttypes.h
2501
Sí, pero todos los enteros de varargs se promueven implícitamente a int.
Timmmm
Eso puede ser, pero en la medida en que C esté definido, el comportamiento no está definido si no usa el especificador correcto.
2501
Pero% x es el especificador correcto. ( chary unsigned charse promueve a int) [ en.cppreference.com/w/cpp/language/variadic_arguments] . Solo necesitaría usar los especificadores PRI para cosas que no encajan en su plataforma int, por ejemplo unsigned int.
Timmmm
%xes correcto para unsigned int no int. Los tipos char y unsigned char se promueven a int. Además, no hay garantía de que uint8_t esté definido como carácter sin firmar.
2501
2

Probablemente esté imprimiendo desde una matriz de caracteres firmada. Imprima desde una matriz de caracteres sin firmar o enmascare el valor con 0xff: por ejemplo, ar [i] y 0xFF. Los valores de c0 se extienden en signo porque el bit alto (signo) está establecido.

Richard Pennington
fuente
-1

Intente algo como esto:

int main()
{
    printf("%x %x %x %x %x %x %x %x\n",
        0xC0, 0xC0, 0x61, 0x62, 0x63, 0x31, 0x32, 0x33);
}

Que produce esto:

$ ./foo 
c0 c0 61 62 63 31 32 33
OscuroRobot
fuente