Impressão de caracteres hexadecimais em C

103

Estou tentando ler uma linha de caracteres e imprimir o equivalente hexadecimal dos caracteres.

Por exemplo, se eu tenho uma string em que "0xc0 0xc0 abc123"os 2 primeiros caracteres estão c0em hexadecimal e os caracteres restantes estão abc123em ASCII, então devo obter

c0 c0 61 62 63 31 32 33

No entanto, printfusar %xme dá

ffffffc0 ffffffc0 61 62 63 31 32 33

Como obtenho a saída desejada sem o "ffffff"? E por que apenas c0 (e 80) tem o ffffff, mas não os outros caracteres?

Rayne
fonte
A string que corresponde à sua matriz de bytes seria ..."\xc0\xc0abc123"
burito

Respostas:

132

Você está vendo o ffffffporque charestá conectado ao seu sistema. Em C, funções vararg como printfpromoverão todos os inteiros menores que inta int. Como charé um número inteiro (número inteiro assinado de 8 bits no seu caso), seus caracteres estão sendo promovidos por intmeio da extensão de sinal.

Como c0e 80têm um 1 bit à esquerda (e são negativos como um inteiro de 8 bits), eles estão sendo estendidos de 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 inferiores que você deseja.

Mysticial
fonte
15
Minha solução usando um cast para unsigned charé uma instrução menor em gcc4.6 para x86-64 ...
lvella
1
Talvez eu possa ajudar. Este é (tecnicamente) um comportamento indefinido porque o especificador xrequer um tipo sem sinal, mas ch é promovido para int. O código correto seria simplesmente lançar ch para não assinado, ou usar um elenco de unsigned char e o especificador: hhx.
2501
1
Se sim printf("%x", 0), nada é impresso.
Gustavo Meira
Ele não imprime nada porque o mínimo está definido como 0. Para corrigir isso, tente o printf("%.2x", 0);qual aumentará os caracteres mínimos desenhados para 2. Para definir um máximo, acrescente o. com um número. Por exemplo, você pode forçar apenas 2 caracteres desenhados fazendoprintf("%2.2x", 0);
user2262111
Qualquer razão pela qual printf("%x", ch & 0xff)deveria ser melhor do que apenas usar printf("%02hhX", a)a resposta de @brutal_lobster ?
maxschlepzig
62

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

printf("%hhX", a);

Na maioria dos casos, você desejará definir o comprimento mínimo também para preencher o segundo caractere com zeros:

printf("%02hhX", a);

ISO / IEC 9899: 201x diz:

7 Os modificadores de comprimento e seus significados são: hh Especifica que um seguinte especificador de conversão d, i, o, u, x ou X se aplica a um argumento de char assinado ou unsigned char (o argumento terá sido promovido de acordo com as promoções inteiras, mas seu valor deve ser convertido para assinado char ou unsigned char antes da impressão); ou que um seguinte

brutal_lobster
fonte
30

Você pode criar um caractere não assinado:

unsigned char c = 0xc5;

Imprimir vai dar C5e não ffffffc5.

Apenas os caracteres maiores que 127 são impressos com o ffffffporque eles são negativos (o caractere é assinado).

Ou você pode lançar o chardurante a impressão:

char c = 0xc5; 
printf("%x", (unsigned char)c);
Hicham
fonte
3
+1 a melhor resposta real, digitação explícita o mais próximo possível da declaração de dados (mas não mais perto).
Bob Stein
13

Você provavelmente está armazenando o valor 0xc0 em uma charvariável, que provavelmente é um tipo com sinal , e seu valor é negativo (o conjunto de bits mais significativo). Então, ao imprimir, ele é convertido em int, e para manter a equivalência semântica, o compilador preenche os bytes extras com 0xff, então o negativo intterá o mesmo valor numérico do seu negativo char. Para corrigir isso, basta lançar para unsigned charao imprimir:

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

Você pode usar hhpara dizer printfque o argumento é um char sem sinal. Use 0para obter preenchimento zero e 2definir a largura como 2. xou Xpara caracteres hexadecimais em maiúsculas / minúsculas.

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 este não é, de alguma forma, os especificadores de formato 'corretos', sugiro que leiam o printflink novamente. Especificamente:

Mesmo que% c espere um argumento int, é seguro passar um caractere por causa da promoção de inteiro que ocorre quando uma função variável é chamada.

As especificações de conversão corretas para os tipos de caracteres de largura fixa (int8_t, etc) são definidas no cabeçalho <cinttypes>(C ++) ou <inttypes.h>(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, pois os valores devem ser sempre positivos e facilmente caber em um int assinado. De qualquer maneira, não há especificador de formato hexideximal assinado.

Edição 2 : ( edição "quando-admitir-que-você-está-errado"):

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

hh: especifica que a seguinte d, i, o, u, x, ou Xespecificador de conversão aplica-se a um signed charou unsigned charargumento (o argumento terá sido promovidos de acordo com as promoções de inteiros, mas o seu valor deve ser convertido em signed charou unsigned charantes da impressão); ou que um nespecificador de conversão seguinte se aplica a um ponteiro para um signed charargumento.

Timmmm
fonte
Os especificadores não são corretos para o tipo uint8_t. Os tipos de largura fixa usam especificadores de impressão especiais. Veja:inttypes.h
2501
Sim, mas todos os inteiros varargs são implicitamente promovidos para int.
Timmmm
Isso pode ser, mas até onde C é definido, o comportamento é indefinido se você não usar o especificador correto.
2501
Mas% x é o especificador correto. ( chare unsigned charsão promovidos a int) [ en.cppreference.com/w/cpp/language/variadic_arguments] . Você só precisaria usar os especificadores PRI para coisas que não cabem em sua plataforma int- por exemplo unsigned int.
Timmmm
%xestá correto para unsigned int e não int. Os tipos char e unsigned char são promovidos a int. Além disso, não há garantia de que uint8_t seja definido como unsigned char.
2501 de
2

Provavelmente, você está imprimindo de uma matriz de caracteres assinada. Imprima a partir de um array de caracteres sem sinal ou mascare o valor com 0xff: por exemplo, ar [i] & 0xFF. Os valores c0 estão sendo estendidos por sinal porque o bit de sinal alto está definido.

Richard Pennington
fonte
-1

Experimente 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
ObscureRobot
fonte