1
votes

l'abstraction C ++ Endian est-elle neutre?

Supposons que j'ai un client et un serveur qui communiquent entre eux des nombres de 16 bits via certains protocoles réseau, disons par exemple ModbusTCP, mais le protocole n'est pas pertinent ici.

Maintenant, je sais que l'extrémité du client est petit (mon PC) et le boutian du serveur est gros (certains automates), le client est entièrement écrit en C ++ avec des sockets Boost Asio. Avec cette configuration, j'ai pensé que je devais permuter les octets reçus du serveur pour stocker correctement le numéro dans une variable uint16_t, mais c'est faux car je lis des valeurs incorrectes.

Jusqu'à présent, je crois comprendre que mon abstraction C ++ stocke correctement les valeurs dans des variables sans que je me soucie réellement de l'échange ou de l'endianité. Considérez cet extrait de code:

// take the address of val, convert it to uint8_t pointer
auto addr = static_cast<uint8_t*>(&val);
// take the first and second bytes and print them 
printf ("%d ", (int)addr[0]);   // print 1
printf ("%d", (int)addr[1]);    // print 2

Cela m'a quelque peu surpris, aussi parce que si je regarde dans la représentation mémoire avec des pointeurs, j'ai trouvé qu'ils sont en fait stockés en little endian sur le client:

// received 0x0201  (513 in big endian)
uint8_t high { 0x02 };  // first byte
uint8_t low { 0x01 };   // second byte
// merge into 16 bit value (no swap)
uint16_t val = (static_cast<uint16_t>(high)<< 8) | (static_cast<uint16_t>(low));
std::cout<<val;   //correctly prints 513

La question est donc la suivante:

Tant que je ne gâche pas avec les adresses mémoire et les pointeurs, C ++ peut garantissez-moi que les valeurs que je lis sur le réseau sont correctes, quel que soit l'extrémité du serveur, n'est-ce pas? Ou il me manque quelque chose ici?

MODIFIER: Merci pour les réponses, je souhaite ajouter que j'utilise actuellement boost :: asio :: write (socket, boost :: asio :: buffer (data)) pour envoyer des données du client à le serveur et les données sont un std::vector . Donc, je crois comprendre que tant que je remplis les données dans l'ordre du réseau, je ne devrais pas me soucier de l'endianness de mon système (ou même du serveur pour les données 16 bits), car j'opère sur les "valeurs" et je ne lis pas directement les octets de mémoire, non?

Pour utiliser la famille de fonctions htons , je dois changer ma couche TCP sous-jacente pour utiliser memcpy ou similaire et un uint8_t * tampon de données, c'est plus C-esque que C ++ ish, pourquoi devrais-je le faire? y a-t-il un avantage que je ne vois pas?


2 commentaires

" C ++ peut me garantir que les valeurs que je lis sur le réseau sont correctes quel que soit le bout du serveur, correct ": non, avoir la même définition de source dans le serveur et le client (s ) et assurez-vous de lire les entiers dans le bon ordre, une façon est de toujours utiliser des fonctions comme ntohl et reverse lorsque vous écrivez et lisez pour leur demander de faire le travail, car ils savent si vous êtes en petit / gros boutiste


Si vous deviez «sérialiser» un objet sur une petite machine endian en prenant ses octets dans l'ordre, puis désérialiser cette transmission réseau sur une machine big endian, l'ordre serait absolument important (en supposant que l'alignement et le remplissage ne sont pas entrés le chemin d'abord). C'est pourquoi nous ne sérialisons pas comme ça, et c'est pourquoi vous devez définir l'ordre des octets de votre réseau lors de l'écriture d'un protocole de communication. Votre protocole de communication (boost et / ou modbus) s'en chargera probablement pour vous.


3 Réponses :


2
votes

(static_cast (élevé) (low)) a le même comportement quelle que soit l'endianité, l'extrémité "gauche" d'un nombre sera toujours le bit le plus significatif, l'endianness ne change que si ce bit est dans le premier ou dans le dernier octet.

Par exemple:

uint16_t input = 0x0201; // input is in host order
uint16_t networkInput = htons(input);
uint8_t data[2];
memcpy(data, &networkInput , sizeof(networkInput));
// data is big endian or "network" order
uint16_t networkOutput;
memcpy(&networkOutput, &data, sizeof(networkOutput));
uint16_t output = ntohs(networkOutput);  // output is in host order

La même chose s'applique dans l'autre sens:

uint8_t data[] = {0x02, 0x01};
uint16_t output1;
memcpy(&output1, data, sizeof(output1)); // will be 0x0102 or 0x0201 depending on endianness
uint16_t output2 = data[1] << 8 | data[0]; // will be 0x0201 regardless of endianness

Pour garantir votre code fonctionne sur toutes les plates-formes de son mieux pour utiliser les familles de fonctions htons et ntohs :

uint16_t input = 0x0201;
uint8_t leftByte = input >> 8; // same result regardless of endianness
uint8_t rightByte = input & 0xFF; // same result regardless of endianness
uint8_t data[2];
memcpy(data, &input, sizeof(input)); // data will be {0x02, 0x01} or {0x01, 0x02} depending on endianness


1 commentaires

Je vois, mais je n'utilise pas du tout memcpy, j'utilise boost :: asio :: write (socket, boost :: asio :: buffer (data)); , où data est std :: vector . Donc, tant que je remplis les données dans l'ordre du réseau, je ne devrais pas utiliser htons , non?



1
votes

Le premier fragment de votre code fonctionne correctement car vous ne travaillez pas directement avec des adresses d'octets. Ce code est compilé pour avoir un résultat de fonctionnement correct indépendamment de votre plate-forme ENDIANness en raison de la définition des opérateurs «<<» et «|» par langage C ++.

Le deuxième fragment de votre code le prouve, montrant les valeurs réelles d'octets séparés sur votre système little-endian.

Le réseau TCP / IP standardise l'utilisation du format big-endian et fournit les utilitaires suivants:

  • avant d'envoyer des valeurs numériques multi-octets, utilisez les fonctions standard: htonl ("host-to-network-long") et htons ("host-to-netowrk-short") pour convertir vos valeurs en représentation réseau,
  • après avoir reçu des valeurs numériques multi-octets, utilisez les fonctions standard: ntohl ("network-to-host-long") et ntohs ("network-to-host-short") pour convertir vos valeurs en une représentation spécifique à votre plate-forme.

(En fait, ces 4 utilitaires effectuent des conversions sur les plates-formes little-endian uniquement et ne font rien sur les plates-formes big-endial. Mais les utiliser toujours rend votre code indépendant de la plate-forme.

Avec ASIO, vous avez accès à ces utilitaires en utilisant: #include

Pour en savoir plus, recherchez le sujet "man htonl" ou "msdn htonl" dans Google.


0 commentaires

1
votes

À propos de Modbus:

Pour les mots de 16 bits, Modbus envoie d'abord l'octet le plus significatif, cela signifie qu'il utilise Big-Endian, puis si le client ou le serveur utilise Little-Endian, ils devront échanger les octets lors de l'envoi ou de la réception.

Un autre problème est que Modbus ne définit pas dans quel ordre les registres 16 bits sont envoyés pour les types 32 bits.

Il existe des serveurs Modbus qui envoient en premier le registre 16 bits le plus significatif et d'autres qui font le contraire. Pour cela, la seule solution est d'avoir dans la configuration client la possibilité de permuter les registres 16 bits.

Un problème similaire peut également se produire lorsque des chaînes de caractères sont transmises, certains serveurs au lieu d'envoyer abcdef envoient badcfe


1 commentaires

à droite, pour les données 32 bits, je pourrais avoir besoin d'effectuer un échange de mots pour obtenir BADC ou CDAB, merci