Question

J'essaie de convertir une structure en tableau de caractères à envoyer sur le réseau. Cependant, le tableau de caractères génère une sortie étrange lorsque je le fais.

#include <stdio.h>

struct x
{
   int x;
} __attribute__((packed));


int main()
{
   struct x a;
   a.x=127;
   char *b = (char *)&a;
   int i;
   for (i=0; i<4; i++)
      printf("%02x ", b[i]);
   printf("\n");
   for (i=0; i<4; i++)
      printf("%d ", b[i]);
   printf("\n");
   return 0;
}

Voici la sortie pour différentes valeurs de a.x (sur un X86 utilisant gcc):
127:
7f 00 00 00
127 0 0 0

128:
ffffff80 00 00 00
-128 0 0 0

255:
ffffffff 00 00 00
-1 0 0 0

256:
00 01 00 00
0 1 0 0

Je comprends les valeurs pour 127 et 256, mais pourquoi les chiffres changent-ils lorsqu'on passe à 128? Pourquoi ne serait-ce pas simplement: 80 00 00 00 128 0 0 0

Est-ce que j'oublie de faire quelque chose dans le processus de conversion ou est-ce que j'oublie quelque chose au sujet de la représentation entière?

* Remarque: il ne s'agit que d'un petit programme de test. Dans un programme réel, j'ai plus dans la structure, de meilleurs noms de variables et je convertis en little-endian.
* Edit: mise en forme

Était-ce utile?

La solution

Le spécificateur de format x indique à lui seul que l'argument est un int , et comme le nombre est négatif, printf requiert huit caractères affiche les quatre octets non nuls de la valeur int . Le modificateur 0 indique de compléter la sortie avec des zéros, et le modificateur 2 indique que la sortie minimum doit comporter deux caractères. Autant que je sache, printf ne permet pas de spécifier une largeur maximale , sauf pour les chaînes.

Maintenant, vous ne faites que passer un char , donc nu x indique à la fonction d'utiliser l'intégralité du int qui a été passé à la place. - en raison de la promotion de l’argument par défaut pour " ... " paramètres. Essayez le modificateur hh pour indiquer à la fonction de traiter l'argument comme un simple char :

printf("%02hhx", b[i]);

Autres conseils

Ce que vous voyez est le signe préservant la conversion de char en int. Le comportement résulte du fait que char est signé sur votre système ( Remarque: char n'est pas signé sur tous les systèmes). Cela conduira à des valeurs négatives si un motif de bits donne une valeur négative pour un caractère. Promouvoir un tel caractère dans un int préservera le signe et l'int sera négatif aussi. Notez que même si vous ne mettez pas explicitement un (int) , le compilateur promouvra automatiquement le caractère à un int lorsqu'il passera à printf. La solution consiste à convertir votre valeur en caractère non signé en premier:

.
for (i=0; i<4; i++)
   printf("%02x ", (unsigned char)b[i]);

Vous pouvez également utiliser caractère non signé * dès le début:

unsigned char *b = (unsigned char *)&a;

Et vous n'aurez besoin d'aucun casting au moment de l'imprimer avec printf.

char est un type signé; donc avec complément à deux, 0x80 est -128 pour un entier de 8 bits (c'est-à-dire un octet)

Traiter votre structure comme s'il s'agissait d'un tableau de caractères constitue un comportement indéfini. Pour l'envoyer sur le réseau, utilisez plutôt la sérialisation appropriée. C’est pénible en C ++ et plus encore en C, mais c’est la seule façon pour votre application de fonctionner indépendamment des machines en lecture et en écriture.

http://fr.wikipedia.org/wiki/Serialization#C

Convertir votre structure en caractères ou en octets de la manière dont vous le faites va créer des problèmes lorsque vous essayez de le rendre neutre du point de vue du réseau. Pourquoi ne pas aborder ce problème maintenant? Il existe diverses techniques que vous pouvez utiliser, qui sont toutes probablement plus "portables". que ce que vous essayez de faire. Par exemple:

  • L’envoi de données numériques sur le réseau, indépendamment de la machine, a longtemps été traité, dans le monde POSIX / Unix, via les fonctions htonl , htons , < code> ntohl et ntohs . Voir, par exemple, le byteorder (3) sur un système FreeBSD ou Linux.
  • La conversion de données vers et depuis une représentation totalement neutre telle que JSON est également parfaitement acceptable. Le temps que vos programmes consacrent à la conversion des données entre JSON et les formulaires natifs risque d’être minime par rapport aux latences de transmission réseau.

char est un type signé, donc ce que vous voyez est la représentation en deux compliments. Transformer en (unsigned char *) résoudra ce problème (Rowland vient de me battre).

Sur une note de côté, vous voudrez peut-être changer

for (i=0; i<4; i++) {
//...
}

à

for (i=0; i<sizeof(x); i++) {
//...
}

La signature du tableau de caractères n’est pas la source du problème! (C'est un problème, mais pas le seul.)

Alignement! C'est le mot clé ici. C'est pourquoi vous ne devez JAMAIS essayer de traiter les structures comme de la mémoire brute. Les compliers (et divers indicateurs d'optimisation), les systèmes d'exploitation et les phases de la lune font tous des choses étranges et excitantes à l'emplacement actuel, en mémoire de "adjacent". champs dans une structure. Par exemple, si vous avez une structure avec un caractère suivi d'un entier, l'intégralité de la structure sera de huit octets en mémoire: le caractère, 3 octets vides, inutiles, puis 4 octets pour l'int. La machine aime faire de telles choses pour que les structures puissent s’adapter parfaitement aux pages de mémoire, et autres.

Suivez un cours d'introduction à l'architecture des machines dans votre collège local. Pendant ce temps, sérialiser correctement. Ne traitez jamais les structures comme des tableaux de caractères.

Lorsque vous allez l'envoyer, utilisez simplement:

(char *) & CustomPacket

pour convertir. Ça marche pour moi.

Vous pouvez vouloir convertir en un tableau de caractères non signé.

Sauf si vous avez très des mesures convaincantes montrant que chaque octet est précieux, ne le faites pas . Utilisez un protocole ASCII lisible, tel que SMTP , NNTP , ou l'un des nombreux autres protocoles Internet de qualité codés par l'IETF.

Si vous devez réellement disposer d'un format binaire, il n'est toujours pas prudent de supprimer les octets d'une structure, car l'ordre des octets, la taille de base ou les contraintes d'alignement peuvent différer d'un hôte à l'autre. Vous devez concevoir votre protocole de fil pour qu'il utilise des tailles bien définies et un ordre d'octets bien défini. Pour votre implémentation, utilisez des macros telles que ntohl (3) ou utilisez le décalage et le masquage pour insérer des octets dans votre flux. Quoi que vous fassiez, assurez-vous que votre code produit les mêmes résultats sur les hôtes big-endian et little-endian.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top