Imprimindo caracteres hexadecimais em C

Estou tentando ler em uma linha de caracteres e, em seguida, imprimo o equivalente hexadecimal dos caracteres.

Por exemplo, se eu tenho uma string que é "0xc0 0xc0 abc123" , onde os primeiros 2 caracteres são c0 em hexadecimal e os caracteres restantes são abc123 em ASCII, então eu deveria obter

 c0 c0 61 62 63 31 32 33 

No entanto, printf usando %x me dá

 ffffffc0 ffffffc0 61 62 63 31 32 33 

Como obtenho a saída que quero sem o "ffffff" ? E porque é que apenas c0 (e 80) tem o ffffff , mas não os outros personagens?

Você está vendo o ffffff porque o char está assinado no seu sistema. Em C, funções vararg como printf promoverão todos os inteiros menores que int para int . Como char é um inteiro (inteiro assinado de 8 bits no seu caso), seus caracteres estão sendo promovidos para int via extensão de sinal.

Já que c0 e 80 têm um 1-bit inicial (e são negativos como um inteiro de 8 bits), eles estão sendo estendidos por sinal enquanto os outros em sua amostra não.

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

Aqui está uma solução:

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

Isso irá mascarar os bits superiores e manter apenas os 8 bits mais baixos que você deseja.

Na verdade, há conversão de tipo para int. Além disso, você pode forçar o tipo a char usando o especificador% hhx.

 printf("%hhX", a); 

Você pode criar um char não assinado:

 unsigned char c = 0xc5; 

A impressão dará C5 e não ffffffc5 .

Somente os caracteres maiores que 127 são impressos com ffffff porque são negativos (char é assinado).

Ou você pode converter o char durante a impressão:

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

Você provavelmente está armazenando o valor 0xc0 em uma variável char , o que provavelmente é um tipo assinado e seu valor é negativo (conjunto de bits mais significativo). Em seguida, ao imprimir, ele é convertido em int e, para manter a equivalência semântica, o compilador preenche os bytes extras com 0xff, portanto, o int negativo terá o mesmo valor numérico de seu char negativo. Para corrigir isso, apenas casting para unsigned char ao imprimir:

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

Você pode usar hh para dizer ao printf que o argumento é um caractere não assinado. Use 0 para obter preenchimento zero e 2 para definir a largura como 2. x ou X para caracteres hexadecimais mais baixos ou maiúsculos.

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

Edit : Se os leitores estão preocupados com a afirmação de 2501 de que isso não é de alguma forma dos especificadores de formato ‘corretos’, eu sugiro que eles leiam o link printf novamente. Especificamente:

Mesmo que% c espere o argumento int, é seguro passar um caractere por causa da promoção de número inteiro que ocorre quando uma function variadic é chamada.

As especificações de conversão corretas para os tipos de caracteres de largura fixa (int8_t, etc) são definidas no header (C ++) ou (C) (embora PRIdMAX, PRIuMAX, etc seja sinônimo de% jd,% ju, etc) .

Quanto ao seu ponto sobre assinado vs não assinado, neste caso, não importa, uma vez que os valores devem ser sempre positivos e facilmente caber em um int assinado. Não há nenhum especificador de formato hexidimal assinado de qualquer forma.

Editar 2 : (“quando-para-admitir-você está errado” edição):

Se você ler o padrão C11 real na página 311 (329 do PDF), você encontrará:

hh: especifica que o seguinte especificador de conversão d , i , o , u , x ou X se aplica a um argumento de unsigned char ou unsigned char (o argumento terá sido promovido de acordo com as promoções de inteiros, mas seu valor será convertido em sinal signed char ou unsigned char antes da impressão); ou que um seguinte especificador de conversão n se aplique a um ponteiro para um argumento de signed char .

Você provavelmente está imprimindo de uma matriz de caracteres assinada. Imprima a partir de uma matriz de caracteres não assinados ou mascare o valor com 0xff: por exemplo, ar [i] e 0xFF. Os valores c0 estão sendo sinalizados porque o bit alto (sinal) está definido.

Tente algo assim:

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

O que produz isso:

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