J'ai besoin d'écrire une fonction pour convertir big endian en little endian en C. Je ne peux utiliser aucune fonction de bibliothèque.
J'ai besoin d'écrire une fonction pour convertir big endian en little endian en C. Je ne peux utiliser aucune fonction de bibliothèque.
Réponses:
En supposant que vous ayez besoin d'un simple échange d'octets, essayez quelque chose comme
Conversion 16 bits non signée:
swapped = (num>>8) | (num<<8);
Conversion 32 bits non signée:
swapped = ((num>>24)&0xff) | // move byte 3 to byte 0
((num<<8)&0xff0000) | // move byte 1 to byte 2
((num>>8)&0xff00) | // move byte 2 to byte 1
((num<<24)&0xff000000); // byte 0 to byte 3
Cela permute les ordres d'octets des positions 1234 à 4321. Si votre entrée l'était 0xdeadbeef
, un swap endian 32 bits pourrait avoir une sortie de 0xefbeadde
.
Le code ci-dessus devrait être nettoyé avec des macros ou au moins des constantes au lieu de nombres magiques, mais j'espère que cela aide tel quel
EDIT: comme une autre réponse l'a souligné, il existe des alternatives spécifiques à la plate-forme, au système d'exploitation et au jeu d'instructions qui peuvent être BEAUCOUP plus rapides que les précédentes. Dans le noyau Linux, il y a des macros (cpu_to_be32 par exemple) qui gèrent assez bien l'endianness. Mais ces alternatives sont spécifiques à leurs environnements. Dans la pratique, l'endianité est mieux traitée en utilisant un mélange d'approches disponibles
((num & 0xff) >> 8) | (num << 8)
, gcc 4.8.3 génère une seule rol
instruction. Et si la conversion 32 bits est écrite comme ((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24)
, le même compilateur génère une seule bswap
instruction.
struct byte_t reverse(struct byte_t b) { struct byte_t rev; rev.ba = b.bh; rev.bb = b.bg; rev.bc = b.bf; rev.bd = b.be; rev.be = b.bd; rev.bf = b.bc; rev.bg = b.bb; rev.bh = b.ba; return rev;}
où c'est un champ de bits avec 8 champs de 1 bit chacun. Mais je ne sais pas si c'est aussi rapide que les autres suggestions. Pour les entiers, utilisez union { int i; byte_t[sizeof(int)]; }
pour inverser octet par octet dans l'entier.
En incluant:
#include <byteswap.h>
vous pouvez obtenir une version optimisée des fonctions de permutation d'octets dépendant de la machine. Ensuite, vous pouvez facilement utiliser les fonctions suivantes:
__bswap_32 (uint32_t input)
ou
__bswap_16 (uint16_t input)
#include <byteswap.h>
, voir le commentaire dans le fichier .h lui-même. Ce message contient des informations utiles, j'ai donc voté à la hausse malgré que l'auteur ignore l'exigence de l'OP de ne pas utiliser une fonction lib.
#include <stdint.h>
//! Byte swap unsigned short
uint16_t swap_uint16( uint16_t val )
{
return (val << 8) | (val >> 8 );
}
//! Byte swap short
int16_t swap_int16( int16_t val )
{
return (val << 8) | ((val >> 8) & 0xFF);
}
//! Byte swap unsigned int
uint32_t swap_uint32( uint32_t val )
{
val = ((val << 8) & 0xFF00FF00 ) | ((val >> 8) & 0xFF00FF );
return (val << 16) | (val >> 16);
}
//! Byte swap int
int32_t swap_int32( int32_t val )
{
val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF );
return (val << 16) | ((val >> 16) & 0xFFFF);
}
Mise à jour : Ajout de l'échange d'octets 64 bits
int64_t swap_int64( int64_t val )
{
val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL );
val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL );
return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL);
}
uint64_t swap_uint64( uint64_t val )
{
val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL );
val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL );
return (val << 32) | (val >> 32);
}
int32_t
et int64_t
, quel est le raisonnement derrière le masquage de ... & 0xFFFF
et ... & 0xFFFFFFFFULL
? Y a-t-il quelque chose qui se passe avec l'extension de signe ici que je ne vois pas? Aussi, pourquoi swap_int64
revient-il uint64_t
? Cela ne devrait-il pas être le cas int64_t
?
swap_int64
dans votre réponse. +1 pour la réponse utile, BTW!
LL
sont inutiles dans un (u)swap_uint64()
peu comme un L
n'est pas nécessaire dans (u)swap_uint32()
. Le U
n'est pas nécessaire dans un uswap_uint64()
peu comme le U
n'est pas nécessaire dansuswap_uint32()
Voici une version assez générique; Je ne l'ai pas compilé, donc il y a probablement des fautes de frappe, mais vous devriez avoir l'idée,
void SwapBytes(void *pv, size_t n)
{
assert(n > 0);
char *p = pv;
size_t lo, hi;
for(lo=0, hi=n-1; hi>lo; lo++, hi--)
{
char tmp=p[lo];
p[lo] = p[hi];
p[hi] = tmp;
}
}
#define SWAP(x) SwapBytes(&x, sizeof(x));
NB: Ceci n'est pas optimisé pour la vitesse ou l'espace. Il se veut clair (facile à déboguer) et portable.
Mise à jour 04/04/2018 Ajout de assert () pour piéger le cas invalide de n == 0, comme repéré par le commentateur @chux.
bswap
instruction par un compilateur X86 décent avec l'optimisation activée. Cette version avec un paramètre pour la taille ne pouvait pas faire cela.
Si vous avez besoin de macros (par exemple, système embarqué):
#define SWAP_UINT16(x) (((x) >> 8) | ((x) << 8))
#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24))
UINT
dans leur nom.
Edit: ce sont des fonctions de bibliothèque. Les suivre est la manière manuelle de le faire.
Je suis absolument abasourdi par le nombre de personnes qui ne connaissent pas __byteswap_ushort, __byteswap_ulong et __byteswap_uint64 . Bien sûr, ils sont spécifiques à Visual C ++, mais ils se compilent en un code délicieux sur les architectures x86 / IA-64. :)
Voici une utilisation explicite de l' bswap
instruction, tirée de cette page . Notez que la forme intrinsèque ci-dessus sera toujours plus rapide que cela , je l'ai seulement ajoutée pour donner une réponse sans routine de bibliothèque.
uint32 cq_ntohl(uint32 a) {
__asm{
mov eax, a;
bswap eax;
}
}
Comme une blague:
#include <stdio.h>
int main (int argc, char *argv[])
{
size_t sizeofInt = sizeof (int);
int i;
union
{
int x;
char c[sizeof (int)];
} original, swapped;
original.x = 0x12345678;
for (i = 0; i < sizeofInt; i++)
swapped.c[sizeofInt - i - 1] = original.c[i];
fprintf (stderr, "%x\n", swapped.x);
return 0;
}
int i, size_t sizeofInt
et pas du même type pour les deux.
voici un moyen d'utiliser l'instruction SSSE3 pshufb en utilisant son Intel intrinsèque, en supposant que vous ayez un multiple de 4 int
s:
unsigned int *bswap(unsigned int *destination, unsigned int *source, int length) {
int i;
__m128i mask = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
for (i = 0; i < length; i += 4) {
_mm_storeu_si128((__m128i *)&destination[i],
_mm_shuffle_epi8(_mm_loadu_si128((__m128i *)&source[i]), mask));
}
return destination;
}
Est-ce que cela fonctionnera / sera plus rapide?
uint32_t swapped, result;
((byte*)&swapped)[0] = ((byte*)&result)[3];
((byte*)&swapped)[1] = ((byte*)&result)[2];
((byte*)&swapped)[2] = ((byte*)&result)[1];
((byte*)&swapped)[3] = ((byte*)&result)[0];
char
, non byte
.
Voici une fonction que j'ai utilisée - testée et fonctionne sur n'importe quel type de données de base:
// SwapBytes.h
//
// Function to perform in-place endian conversion of basic types
//
// Usage:
//
// double d;
// SwapBytes(&d, sizeof(d));
//
inline void SwapBytes(void *source, int size)
{
typedef unsigned char TwoBytes[2];
typedef unsigned char FourBytes[4];
typedef unsigned char EightBytes[8];
unsigned char temp;
if(size == 2)
{
TwoBytes *src = (TwoBytes *)source;
temp = (*src)[0];
(*src)[0] = (*src)[1];
(*src)[1] = temp;
return;
}
if(size == 4)
{
FourBytes *src = (FourBytes *)source;
temp = (*src)[0];
(*src)[0] = (*src)[3];
(*src)[3] = temp;
temp = (*src)[1];
(*src)[1] = (*src)[2];
(*src)[2] = temp;
return;
}
if(size == 8)
{
EightBytes *src = (EightBytes *)source;
temp = (*src)[0];
(*src)[0] = (*src)[7];
(*src)[7] = temp;
temp = (*src)[1];
(*src)[1] = (*src)[6];
(*src)[6] = temp;
temp = (*src)[2];
(*src)[2] = (*src)[5];
(*src)[5] = temp;
temp = (*src)[3];
(*src)[3] = (*src)[4];
(*src)[4] = temp;
return;
}
}
source
est aligné selon les besoins - mais si cette hypothèse ne tient pas, le code est UB.
EDIT: Cette fonction n'échange que l'endianité des mots alignés de 16 bits. Une fonction souvent nécessaire pour les encodages UTF-16 / UCS-2. MODIFIER FIN.
Si vous souhaitez modifier la finalité d'un bloc de mémoire, vous pouvez utiliser mon approche extrêmement rapide. Votre matrice de mémoire doit avoir une taille multiple de 8.
#include <stddef.h>
#include <limits.h>
#include <stdint.h>
void ChangeMemEndianness(uint64_t *mem, size_t size)
{
uint64_t m1 = 0xFF00FF00FF00FF00ULL, m2 = m1 >> CHAR_BIT;
size = (size + (sizeof (uint64_t) - 1)) / sizeof (uint64_t);
for(; size; size--, mem++)
*mem = ((*mem & m1) >> CHAR_BIT) | ((*mem & m2) << CHAR_BIT);
}
Ce type de fonction est utile pour modifier l'extrémité des fichiers Unicode UCS-2 / UTF-16.
t know if it
aussi rapide que les suggestions mais ça marche: github.com/heatblazer/helpers/blob/master/utils.h
CHAR_BIT
au lieu de 8
est curieux comme 0xFF00FF00FF00FF00ULL
dépend de CHAR_BIT == 8
. Notez que ce LL
n'est pas nécessaire dans la constante.
CHAR_BIT
pour augmenter l'exposition de cette macro. Quant au LL, c'est plus une annotation qu'autre chose. C'est aussi une habitude que j'ai prise il y a longtemps avec des compilateurs buggy (pré-standard) qui ne feraient pas la bonne chose.
Cet extrait de code peut convertir un petit nombre Endian 32 bits en un nombre Big Endian.
#include <stdio.h>
main(){
unsigned int i = 0xfafbfcfd;
unsigned int j;
j= ((i&0xff000000)>>24)| ((i&0xff0000)>>8) | ((i&0xff00)<<8) | ((i&0xff)<<24);
printf("unsigned int j = %x\n ", j);
}
((i>>24)&0xff) | ((i>>8)&0xff00) | ((i&0xff00)<<8) | (i<<24);
peut être plus rapide sur certaines plates-formes (par exemple, le recyclage des constantes de masque AND). La plupart des compilateurs le feraient, cependant, mais certains compilateurs simples ne sont pas en mesure de l'optimiser pour vous.
Si vous utilisez un processeur x86 ou x86_64, le big endian est natif. alors
pour les valeurs 16 bits
unsigned short wBigE = value;
unsigned short wLittleE = ((wBigE & 0xFF) << 8) | (wBigE >> 8);
pour les valeurs 32 bits
unsigned int iBigE = value;
unsigned int iLittleE = ((iBigE & 0xFF) << 24)
| ((iBigE & 0xFF00) << 8)
| ((iBigE >> 8) & 0xFF00)
| (iBigE >> 24);
Ce n'est pas la solution la plus efficace, sauf si le compilateur reconnaît qu'il s'agit d'une manipulation au niveau des octets et génère du code de permutation d'octets. Mais cela ne dépend d'aucune astuce de disposition de la mémoire et peut être transformé en macro assez facilement.