Stampa di caratteri esadecimali in C

Sto cercando di leggere una riga di caratteri, quindi stampare l’equivalente esadecimale dei personaggi.

Ad esempio, se ho una stringa che è "0xc0 0xc0 abc123" , dove i primi 2 caratteri sono c0 in esadecimale e i caratteri rimanenti sono abc123 in ASCII, allora dovrei ottenere

 c0 c0 61 62 63 31 32 33 

Tuttavia, mi dà printf usando %x

 ffffffc0 ffffffc0 61 62 63 31 32 33 

Come posso ottenere l’output desiderato senza "ffffff" ? E perché solo c0 (e 80) ha il ffffff , ma non gli altri caratteri?

Stai vedendo il ffffff perché char è firmato sul tuo sistema. In C, le funzioni vararg come printf promuoveranno tutti gli interi minori di int a int . Poiché char è un numero intero (numero int con segno a 8 bit nel tuo caso), i tuoi caratteri vengono promossi a int tramite l’estensione del segno.

Poiché c0 e 80 hanno un primo bit a 1 bit (e sono negativi come un numero intero a 8 bit), vengono estesi dal segno mentre gli altri nel campione non lo fanno.

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

Ecco una soluzione:

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

Questo maschererà i bit superiori e manterrà solo gli 8 bit più bassi che si desidera.

In effetti, c’è la conversione di tipo in int. Inoltre puoi forzare il tipo al carattere usando l’identificatore% hhx.

 printf("%hhX", a); 

Puoi creare un char senza segno:

 unsigned char c = 0xc5; 

La stampa darà C5 e non ffffffc5 .

Solo i caratteri maggiori di 127 vengono stampati con ffffff perché sono negativi (il carattere è firmato).

Oppure puoi lanciare il char durante la stampa:

 char c = 0xc5; printf("%x", (unsigned char)c); 

Probabilmente stai memorizzando il valore 0xc0 in una variabile char , che probabilmente è un tipo firmato e il tuo valore è negativo (bit più significativo). Quindi, quando stampa, viene convertito in int , e per mantenere l’equivalenza semantica, il compilatore riempie i byte extra con 0xff, quindi l’ int negativo avrà lo stesso valore numerico del tuo char negativo. Per risolvere questo problema, basta trasmettere a unsigned char durante la stampa:

 printf("%x", (unsigned char)variable); 

Puoi usare hh per dire a printf che l’argomento è un char senza segno. Usa 0 per ottenere lo zero padding e 2 per impostare la larghezza su 2. x o X per caratteri esadecimali maiuscoli / minuscoli.

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

Modifica : Se i lettori sono preoccupati per l’asserzione di 2501 che questo non è in qualche modo gli specificatori di formato ‘corretto’, suggerisco di leggere di nuovo il link printf . In particolare:

Anche se% c si aspetta un argomento int, è sicuro passare un char a causa della promozione intera che ha luogo quando viene chiamata una funzione variadic.

Le specifiche di conversione corrette per i tipi di carattere a larghezza fissa (int8_t, ecc.) Sono definite nell’intestazione (C ++) o (C) (sebbene PRIdMAX, PRIuMAX, ecc sia sinonimo di% jd,% ju, ecc . ) .

Per quanto riguarda il suo punto su firmato vs non firmato, in questo caso non importa in quanto i valori devono essere sempre positivi e facilmente inseribili in un int firmato. Non esiste comunque un identificatore di formato esagonale firmato.

Modifica 2 : (“quando-ammetti-hai sbagliato” edizione):

Se si legge l’effettivo standard C11 a pagina 311 (329 del PDF), si trova:

hh: specifica che un identificatore di conversione seguente d , i , o , u , x o X applica a un signed char o a un argomento unsigned char (l’argomento sarà promosso in base alle promozioni intere, ma il suo valore sarà convertito in signed char o unsigned char prima della stampa); o che un n conversione n successivo si applica a un puntatore a un argomento signed char .

Probabilmente stai stampando da un array di caratteri firmato. Stampare da un array di char senza segno o mascherare il valore con 0xff: ad esempio ar [i] e 0xFF. I valori c0 vengono firmati estesi perché il bit alto (segno) è impostato.

Prova qualcosa del genere:

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

Che produce questo:

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