Conversion Little Endian en Big Endian

Je veux juste demander si ma méthode est correcte pour convertir de little endian en big endian, juste pour m’assurer que je comprends la différence

J’ai un numéro qui est stocké dans little-endian, voici les représentations binarys et hexagonales du nombre:

‭0001 0010 0011 0100 0101 0110 0111 1000‬ ‭12345678‬ 

Au format big-endian, je pense que les octets doivent être permutés, comme ceci:

 1000 0111 0110 0101 0100 0011 0010 0001 ‭87654321 

Est-ce correct?

De plus, le code ci-dessous tente de le faire mais échoue. Y a-t-il quelque chose qui cloche ou puis-je optimiser quelque chose? Si le code est mauvais pour cette conversion, pouvez-vous expliquer pourquoi et indiquer une meilleure méthode pour effectuer la même conversion?

 uint32_t num = 0x12345678; uint32_t b0,b1,b2,b3,b4,b5,b6,b7; uint32_t res = 0; b0 = (num & 0xf) << 28; b1 = (num & 0xf0) << 24; b2 = (num & 0xf00) << 20; b3 = (num & 0xf000) << 16; b4 = (num & 0xf0000) << 12; b5 = (num & 0xf00000) << 8; b6 = (num & 0xf000000) << 4; b7 = (num & 0xf0000000) << 4; res = b0 + b1 + b2 + b3 + b4 + b5 + b6 + b7; printf("%d\n", res); 

Le code exemple d’OP est incorrect.

La conversion Endian fonctionne au niveau des bits et des octets 8 bits. La plupart des problèmes endian concernent le niveau d’octet. Le code OP effectue un changement final au niveau du quartet 4 bits. Recommander à la place:

 // Swap endian (big to little) or (little to big) uint32_t num = 9; uint32_t b0,b1,b2,b3; uint32_t res; b0 = (num & 0x000000ff) << 24u; b1 = (num & 0x0000ff00) << 8u; b2 = (num & 0x00ff0000) >> 8u; b3 = (num & 0xff000000) >> 24u; res = b0 | b1 | b2 | b3; printf("%" PRIX32 "\n", res); 

Si les performances sont vraiment importantes, le processeur en question devra être connu. Sinon, laissez le compilateur.

[Modifier] OP a ajouté un commentaire qui change les choses.
“La valeur numérique 32 bits représentée par la représentation hexadécimale (st uv wx yz) doit être enregistrée dans un champ de quatre octets sous la forme (st uv wx yz).”

Il semble dans ce cas que l’endian du nombre 32 bits soit inconnu et que le résultat doit être stocké en mémoire dans un ordre peu final.

 uint32_t num = 9; uint8_t b[4]; b[0] = (uint8_t) (num >> 0u); b[1] = (uint8_t) (num >> 8u); b[2] = (uint8_t) (num >> 16u); b[3] = (uint8_t) (num >> 24u); 

[2016 Éditer] Simplification

… Le type du résultat est celui de l’opérande gauche promu …. Opérateurs de décalage binary C11 §6.5.7 3

Utiliser un u après les constantes de décalage (opérandes de droite) donne la même chose que sans.

 b3 = (num & 0xff000000) >> 24u; b[3] = (uint8_t) (num >> 24u); // same as b3 = (num & 0xff000000) >> 24; b[3] = (uint8_t) (num >> 24); 

Je pense que vous pouvez utiliser la fonction htonl() . L’ordre des octets du réseau est big endian.

“J’échange chaque octet non?” -> oui, pour convertir entre le petit et le gros endian, il suffit de donner aux octets l’ordre inverse. Mais au début, réalisez peu de choses:

  • la taille de uint32_t est 32bits, ce qui correspond à 4 octets, soit 8 chiffres HEX
  • masque 0xf récupère les 4 bits les moins significatifs, pour récupérer 8 bits, vous avez besoin de 0xff

Donc, si vous voulez échanger l’ordre de 4 octets avec ce type de masque, vous pouvez:

 uint32_t res = 0; b0 = (num & 0xff) << 24; ; least significant to most significant b1 = (num & 0xff00) << 8; ; 2nd least sig. to 2nd most sig. b2 = (num & 0xff0000) >> 8; ; 2nd most sig. to 2nd least sig. b3 = (num & 0xff000000) >> 24; ; most sig. to least sig. res = b0 | b1 | b2 | b3 ; 

Vous pouvez faire ceci:

 int x = 0x12345678; x = ( x >> 24 ) | (( x << 8) & 0x00ff0000 )| ((x >> 8) & 0x0000ff00) | ( x << 24) ; printf("value = %x", x); // x will be printed as 0x78563412 

Désolé, ma réponse est un peu trop tardive, mais personne ne semble avoir mentionné les fonctions intégrées pour inverser l’ordre des octets, ce qui est très important en termes de performances .

La plupart des processeurs modernes sont little-endian, alors que tous les protocoles réseau sont big-endian. C’est l’histoire et plus encore que vous pouvez trouver sur Wikipedia. Mais cela signifie que nos processeurs convertissent des millions et des millions de fois en petits bouts lorsque nous naviguons sur Internet.

C’est pourquoi la plupart des architectures ont des instructions de processeur dédiées pour faciliter cette tâche. Pour les architectures x86, il existe BSWAP instruction BSWAP et pour les ARM, il existe REV . C’est le moyen le plus efficace d’inverser l’ordre des octets .

Pour éviter l’assemblage dans notre code C, nous pouvons utiliser des composants intégrés. Pour GCC, il existe __builtin_bswap32() et pour Visual C ++, _byteswap_ulong() . Ces fonctions généreront une seule instruction de processeur sur la plupart des architectures.

Voici un exemple:

 #include  #include  int main() { uint32_t le = 0x12345678; uint32_t be = __builtin_bswap32(le); printf("Little-endian: 0x%" PRIx32 "\n", le); printf("Big-endian: 0x%" PRIx32 "\n", be); return 0; } 

Voici la sortie qu’il produit:

 Little-endian: 0x12345678 Big-endian: 0x78563412 

Et voici le déassembly (sans optimisation, ie -O0 ):

  uint32_t be = __builtin_bswap32(le); 0x0000000000400535 <+15>: mov -0x8(%rbp),%eax 0x0000000000400538 <+18>: bswap %eax 0x000000000040053a <+20>: mov %eax,-0x4(%rbp) 

Il n’y a qu’une seule instruction BSWAP .

Donc, si nous nous soucions de la performance , nous devrions utiliser ces fonctions intégrées au lieu de toute autre méthode d’inversion d’octets. Juste mes 2 cents.

Une manière légèrement différente de résoudre ce problème qui peut parfois être utile consiste à avoir une union de la valeur de seize ou de trente-deux bits et un tableau de caractères. Je viens de faire cela lorsque je reçois des messages série entrants avec un ordre big endian, mais je travaille sur un petit micro endian.

union MessageLengthUnion {

 uint16_t asInt; uint8_t asChars[2]; 

};

Puis, lorsque je reçois les messages, je mets le premier reçu uint8 dans .asChars [1], le second dans .asChars [0], puis je l’accède en tant que partie .asInt du syndicat dans le rest de mon programme. Si vous avez une valeur de trente-deux bits à stocker, vous pouvez avoir un tableau long de quatre.

une autre suggestion:

 unsigned int a = 0xABCDEF23; a = ((a&(0x0000FFFF)) << 16) | ((a&(0xFFFF0000)) >> 16); a = ((a&(0x00FF00FF)) << 8) | ((a&(0xFF00FF00)) >>8); printf("%0x\n",a); 

Je suppose que vous êtes sur Linux

Inclure "byteswap.h" & utiliser int32_t bswap_32(int32_t argument);

C’est une vue logique, voir: /usr/include/byteswap.h

Le code de l’OP est incorrect pour les raisons suivantes:

  • Les échanges sont effectués sur une limite de quartet (4 bits) au lieu d’une limite d’octet (8 bits).
  • Les opérations << décalage gauche» << des quatre derniers swaps sont incorrectes, il doit s'agir d'opérations «décalage gauche >> et leurs valeurs de décalage doivent également être corrigées.
  • L'utilisation du stockage intermédiaire n'est pas nécessaire et le code peut donc être réécrit pour être plus concis / reconnaissable. Ce faisant, certains compilateurs seront en mesure d’optimiser davantage le code en reconnaissant le motif souvent utilisé.

Considérez le code suivant, qui convertit efficacement une valeur non signée:

 // Swap endian (big to little) or (little to big) uint32_t num = 0x12345678; uint32_t res = ((num & 0x000000FF) << 16) | ((num & 0x0000FF00) << 8) | ((num & 0x00FF0000) >> 8) | ((num & 0xFF000000) >> 16); printf("%0x\n", res); 

Le résultat est représenté ici en binary et en hexadécimal, remarquez comment les octets ont été échangés:

 ‭0111 1000 0101 0110 0011 0100 0001 0010‬ 78563412 

L'optimisation

En termes de performances, laissez au compilateur le soin d’optimiser votre code lorsque cela est possible. Vous devez éviter les structures de données inutiles telles que les tableaux pour les algorithmes simples tels que celui-ci, car cela entraînera généralement un comportement différent de l'instruction, tel que l'access à la RAM au lieu d'utiliser des registres de la CPU.

Vous pouvez utiliser les fonctions lib. Ils se résument à assembler, mais si vous êtes ouvert à d’autres implémentations en C, les voici (en supposant que int est 32 bits):

 void byte_swap16(unsigned short int *pVal16) { //#define method_one 1 // #define method_two 1 #define method_three 1 #ifdef method_one unsigned char *pByte; pByte = (unsigned char *) pVal16; *pVal16 = (pByte[0] << 8) | pByte[1]; #endif #ifdef method_two unsigned char *pByte0; unsigned char *pByte1; pByte0 = (unsigned char *) pVal16; pByte1 = pByte0 + 1; *pByte0 = *pByte0 ^ *pByte1; *pByte1 = *pByte0 ^ *pByte1; *pByte0 = *pByte0 ^ *pByte1; #endif #ifdef method_three unsigned char *pByte; pByte = (unsigned char *) pVal16; pByte[0] = pByte[0] ^ pByte[1]; pByte[1] = pByte[0] ^ pByte[1]; pByte[0] = pByte[0] ^ pByte[1]; #endif } void byte_swap32(unsigned int *pVal32) { #ifdef method_one unsigned char *pByte; // 0x1234 5678 --> 0x7856 3412 pByte = (unsigned char *) pVal32; *pVal32 = ( pByte[0] << 24 ) | (pByte[1] << 16) | (pByte[2] << 8) | ( pByte[3] ); #endif #if defined(method_two) || defined (method_three) unsigned char *pByte; pByte = (unsigned char *) pVal32; // move lsb to msb pByte[0] = pByte[0] ^ pByte[3]; pByte[3] = pByte[0] ^ pByte[3]; pByte[0] = pByte[0] ^ pByte[3]; // move lsb to msb pByte[1] = pByte[1] ^ pByte[2]; pByte[2] = pByte[1] ^ pByte[2]; pByte[1] = pByte[1] ^ pByte[2]; #endif } 

Et l'utilisation est effectuée comme suit:

 unsigned short int u16Val = 0x1234; byte_swap16(&u16Val); unsigned int u32Val = 0x12345678; byte_swap32(&u32Val); 

Un programme simple C pour convertir de petit à grand

 #include  int main() { unsigned int little=0x1234ABCD,big=0; unsigned char tmp=0,l; printf(" Little endian little=%x\n",little); for(l=0;l < 4;l++) { tmp=0; tmp = little | tmp; big = tmp | (big << 8); little = little >> 8; } printf(" Big endian big=%x\n",big); return 0; }