printf aggiunge `FFFFFF` extra alla stampa esadecimale da un array di caratteri

Si consideri il seguente codice semplificato seguente. Voglio estrarre alcuni dati / stream binari da un file e stamparlo sullo standard output in formato esadecimale.

Ho ricevuto 3 byte extra 0xFFFFFF . Cosa c’è che non va? Da dove vengono i byte extra?

produzione

 in: 2000FFFFFFAF00690033005A00 out: 2000FFFFFFAF00690033005A00 

programma.e

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

Firma l’estensione. Il tuo compilatore sta implementando char come signed char . Quando si passano i caratteri a printf , vengono tutti estesi durante la promozione a int s. Quando il primo bit è uno 0 questo non ha importanza, perché viene esteso con 0 s.

0xAF in binario è 10101111 Poiché il primo bit è un 1 , quando lo si passa a printf viene esteso a tutti 1 s nella conversione in int rendendolo 11111111111111111111111110101111 , il valore esadecimale che si ha.

Soluzione: utilizzare invece il unsigned char per impedire che il modulo di estensione del segno si verifichi nella chiamata

 const unsigned char raw[] = {0x20,0x00,0xAF,0x00,0x69,0x00,0x33,0x00,0x5A,0x00}; 

Tutti questi valori nel tuo esempio originale sono stati ampliati, è solo che 0xAF è l’unico con un 1 nel primo bit.

Un altro esempio più semplice dello stesso comportamento

 signed char c = 0xAF; // probably gives an overflow warning int i = c; // extra 24 bits are all 1 assert( i == 0xFFFFFFAF ); 

Questo perché 0xAF quando convertito da un carattere firmato a un intero con segno è negativo (è esteso al segno) e il formato %02X è per gli argomenti non firmati e stampa il valore convertito come FFFFFFAF .

I caratteri in più vengono visualizzati perché printf %x non troncerà mai automaticamente le cifre su un valore. Anche i valori che non sono negativi ottengono il segno di estensione, ma questo è solo aggiungendo zero bit e il valore si adatta a 2 cifre esadecimali, quindi printf %02 può fare con un output a due cifre.

Notare che ci sono 2 dialetti C: uno in cui è indicato un semplice char e uno in cui non è firmato. Nel tuo è firmato. Puoi cambiarlo usando un’opzione, es. -funsigned-char e clang support -funsigned-char e -fsigned-char .

Printf printf() è una funzione variadica e i suoi argomenti aggiuntivi (corrispondenti a ... parte del suo prototipo) sono soggetti a promozioni di argomenti predefinite , quindi char è promosso a int .

Come il tuo char ha firmato 1 , la rappresentazione del complemento a due è il bit più significativo impostato su uno per l’elemento 0xAF . Durante la promozione il bit firmato viene propagato, risultando 0xFFFFFFAF di tipo int , come presumibilmente sizeof(int) = 4 nella tua implementazione.

Tra l’altro, stai invocando un comportamento indefinito , dal momento che l’ %X formato %X dovrebbe essere usato per l’object di tipo unsigned int o almeno per int con MSB che non è impostato (è pratica comune e ampiamente accettata).

Come suggerito si può prendere in considerazione l’uso di un tipo di unsigned char ambiguo.


1) L’implementazione può scegliere tra la rappresentanza di char firmata e non firmata. È piuttosto comune che il char sia firmato, ma non si può dare per scontato per ogni altro compilatore sul pianeta. Alcuni di essi possono consentire di scegliere tra queste due modalità, come menzionato nella risposta di Jens .