Impression de caractères hexadécimaux en C

103

J'essaie de lire une ligne de caractères, puis d'imprimer l'équivalent hexadécimal des caractères.

Par exemple, si j'ai une chaîne qui est "0xc0 0xc0 abc123", où les 2 premiers caractères sont c0en hexadécimal et les caractères restants sont abc123en ASCII, alors je devrais obtenir

c0 c0 61 62 63 31 32 33

Cependant, l' printfutilisation %xme donne

ffffffc0 ffffffc0 61 62 63 31 32 33

Comment obtenir la sortie souhaitée sans le "ffffff"? Et pourquoi est-ce que seul c0 (et 80) a le ffffff, mais pas les autres caractères?

Rayne
la source
La chaîne qui correspond à votre tableau d'octets serait ..."\xc0\xc0abc123"
burito

Réponses:

132

Vous voyez le ffffffcar charest signé sur votre système. En C, les fonctions vararg telles que printffavoriseront tous les entiers plus petits que intà int. Puisqu'il chars'agit d'un entier (entier signé 8 bits dans votre cas), vos caractères sont promus intvia l'extension de signe.

Puisque c0et 80ont un premier bit de 1 bit (et sont négatifs en tant qu'entier 8 bits), ils sont étendus de signe alors que les autres de votre échantillon ne le font pas.

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

Voici une solution:

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

Cela masquera les bits supérieurs et ne conservera que les 8 bits inférieurs souhaités.

Mysticial
la source
15
Ma solution en utilisant un cast vers unsigned charest une instruction plus petite dans gcc4.6 pour x86-64 ...
lvella
1
Peut-être que je peux aider. Il s'agit d'un comportement (techniquement) indéfini car le spécificateur xnécessite un type non signé, mais ch est promu en int. Le code correct serait tout simplement jeté à ch non signé, ou utiliser un casting pour unsigned char et le prescripteur: hhx.
2501
1
Si je l'ai printf("%x", 0), rien n'est imprimé.
Gustavo Meira
Il n'imprime rien car le minimum est défini sur 0. Pour résoudre ce problème, essayez printf("%.2x", 0);ce qui augmentera le nombre minimum de caractères dessinés à 2. Pour définir un maximum, ajoutez le. avec un nombre. Par exemple, vous ne pouvez forcer que 2 caractères dessinés en faisantprintf("%2.2x", 0);
user2262111
Une raison pour laquelle printf("%x", ch & 0xff)devrait être mieux que d'utiliser simplement printf("%02hhX", a)comme dans la réponse de @ brutal_lobster ?
maxschlepzig le
62

En effet, il existe une conversion de type en int. Vous pouvez également forcer le type à char en utilisant le spécificateur% hhx.

printf("%hhX", a);

Dans la plupart des cas, vous voudrez également définir la longueur minimale pour remplir le deuxième caractère avec des zéros:

printf("%02hhX", a);

ISO / CEI 9899: 201x dit:

7 Les modificateurs de longueur et leurs significations sont: hh Spécifie qu'un spécificateur de conversion d, i, o, u, x ou X suivant s'applique à un argument char signé ou non signé (l'argument aura été promu en fonction des promotions d'entiers, mais sa valeur doit être convertie en caractère signé ou en caractère non signé avant l'impression); ou qu'un suivant

homard_brutal
la source
30

Vous pouvez créer un caractère non signé:

unsigned char c = 0xc5;

L'imprimer donnera C5et non ffffffc5.

Seuls les caractères supérieurs à 127 sont imprimés avec le ffffffcar ils sont négatifs (le caractère est signé).

Ou vous pouvez lancer le chartout en imprimant:

char c = 0xc5; 
printf("%x", (unsigned char)c);
Hicham
la source
3
+1 la vraie meilleure réponse, typage explicite aussi proche que possible de la déclaration de données (mais pas plus proche).
Bob Stein
13

Vous stockez probablement la valeur 0xc0 dans une charvariable, ce qui est probablement un type signé, et votre valeur est négative (ensemble de bits le plus significatif). Ensuite, lors de l'impression, il est converti en int, et pour conserver l'équivalence sémantique, le compilateur remplit les octets supplémentaires avec 0xff, de sorte que le négatif intaura la même valeur numérique que votre négatif char. Pour résoudre ce problème, il vous suffit de lancer unsigned charlors de l'impression:

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

Vous pouvez utiliser hhpour indiquer printfque l'argument est un caractère non signé. Utilisez 0pour obtenir un remplissage nul et 2pour définir la largeur sur 2. xou Xpour les caractères hexadécimaux minuscules / majuscules.

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

Edit : Si les lecteurs sont préoccupés par l'affirmation de 2501 selon laquelle ce n'est pas les spécificateurs de format `` corrects '', je suggère qu'ils relisent le printflien . Plus précisément:

Même si% c attend un argument int, il est sûr de passer un caractère en raison de la promotion d'entiers qui a lieu lorsqu'une fonction variadique est appelée.

Les spécifications de conversion correctes pour les types de caractères à largeur fixe (int8_t, etc.) sont définies dans l'en-tête <cinttypes>(C ++) ou <inttypes.h>(C) (bien que PRIdMAX, PRIuMAX, etc. soit synonyme de% jd,% ju, etc.) .

Quant à son point sur signé vs non signé, dans ce cas, cela n'a pas d'importance car les valeurs doivent toujours être positives et s'intégrer facilement dans un int signé. Il n'y a de toute façon aucun spécificateur de format hexideximal signé.

Edit 2 : ( édition "quand admettre que vous vous trompez"):

Si vous lisez la norme C11 actuelle à la page 311 (329 du PDF), vous trouvez:

hh: Indique que une suite d, i, o, u, x, ou Xspécificateur de conversion applique à une signed charou unsigned charargument (l'argument aura été promu en fonction des promotions entières, mais sa valeur est convertie en signed charou unsigned charavant l' impression); ou qu'un nspécificateur de conversion suivant s'applique à un pointeur vers un signed charargument.

Timmmm
la source
Les spécificateurs ne sont pas corrects pour le type uint8_t. Les types à largeur fixe utilisent des spécificateurs d'impression spéciaux. Voir:inttypes.h
2501
Oui mais tous les entiers varargs sont implicitement promus en int.
Timmmm
C'est peut-être le cas, mais dans la mesure où C est défini, le comportement n'est pas défini si vous n'utilisez pas le bon spécificateur.
2501
Mais% x est le bon spécificateur. ( charet unsigned charsont promus en int) [ en.cppreference.com/w/cpp/language/variadic_arguments] . Vous n'auriez besoin d'utiliser les spécificateurs PRI que pour les choses qui ne correspondent pas à votre plate int- forme , par exemple unsigned int.
Timmmm
%xest correct pour unsigned int pas int. Les types char et unsigned char sont promus en int. De plus, il n'y a aucune garantie que uint8_t soit défini comme un caractère non signé.
2501
2

Vous imprimez probablement à partir d'un tableau de caractères signé. Soit imprimer à partir d'un tableau de caractères non signé, soit masquer la valeur avec 0xff: par exemple ar [i] & 0xFF. Les valeurs c0 sont en cours d'extension de signe car le bit haut (signe) est activé.

Richard Pennington
la source
-1

Essayez quelque chose comme ceci:

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

Ce qui produit ceci:

$ ./foo 
c0 c0 61 62 63 31 32 33
ObscurRobot
la source