Question

Comment convertir des valeurs big-endian et little-endian en C ++?

EDIT: pour plus de clarté, je dois convertir les données binaires (valeurs à virgule flottante double précision et entiers 32 bits et 64 bits) d’une architecture de CPU à une autre. Cela n'implique pas de réseau, donc ntoh () et des fonctions similaires ne fonctionneront pas ici.

EDIT # 2: La réponse que j'ai acceptée s'applique directement aux compilateurs que je cible (c'est pourquoi je l'ai choisie). Cependant, il existe d’autres très bonnes réponses, plus transférables, ici.

Était-ce utile?

La solution

Si vous utilisez Visual C ++ , procédez comme suit: Vous incluez intrin.h et appelez les fonctions suivantes:

Pour les nombres 16 bits:

unsigned short _byteswap_ushort(unsigned short value);

Pour les nombres 32 bits:

unsigned long _byteswap_ulong(unsigned long value);

Pour les nombres 64 bits:

unsigned __int64 _byteswap_uint64(unsigned __int64 value);

Les nombres de 8 bits (caractères) n'ont pas besoin d'être convertis.

De plus, ceux-ci ne sont définis que pour les valeurs non signées, ils fonctionnent également pour les entiers signés.

Pour les flottants et les doubles, c’est plus difficile que pour les entiers simples, car ceux-ci peuvent ou non figurer dans l’ordre des octets de la machine hôte. Vous pouvez obtenir des flotteurs little-endian sur des machines big-endian et inversement.

D'autres compilateurs ont également des propriétés intrinsèques similaires.

Dans GCC , par exemple, vous pouvez appeler directement:

int32_t __builtin_bswap32 (int32_t x)
int64_t __builtin_bswap64 (int64_t x)

(pas besoin d'inclure quelque chose). Afaik bits.h déclare également la même fonction d’une manière non centrée sur gcc.

échange de 16 bits, il s'agit juste d'un peu de rotation.

Appeler les éléments intrinsèques au lieu de faire les vôtres vous procure les meilleures performances et la meilleure densité de code par ailleurs.

Autres conseils

En termes simples:

#include <climits>

template <typename T>
T swap_endian(T u)
{
    static_assert (CHAR_BIT == 8, "CHAR_BIT != 8");

    union
    {
        T u;
        unsigned char u8[sizeof(T)];
    } source, dest;

    source.u = u;

    for (size_t k = 0; k < sizeof(T); k++)
        dest.u8[k] = source.u8[sizeof(T) - k - 1];

    return dest.u;
}

utilisation: swap_endian<uint32_t>(42).

De L'erreur de commande d'octet de Rob Pyke:

  

Supposons que votre flux de données comporte un entier 32 bits codé en little-endian. Voici comment l'extraire (en supposant que des octets non signés):

i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);
  

Si c'est big-endian, voici comment l'extraire:

i = (data[3]<<0) | (data[2]<<8) | (data[1]<<16) | (data[0]<<24);

TL; DR: ne vous inquiétez pas de l'ordre natif de votre plate-forme, tout ce qui compte est l'ordre des octets du flux que vous lisez, et espérez qu'il soit bien défini.

Remarque: dans le commentaire, il a été remarqué qu'en l'absence de conversion de type explicite, il était important que data soit un tableau de unsigned char ou uint8_t. Si vous utilisez signed char ou char (si signé), data[x] sera promu en tant qu'entier et data[x] << 24 décaler éventuellement un 1 dans le bit de signe qui est UB.

Si vous procédez ainsi pour des raisons de compatibilité réseau / hôte, vous devez utiliser:

ntohl() //Network to Host byte order (Long)
htonl() //Host to Network byte order (Long)

ntohs() //Network to Host byte order (Short)
htons() //Host to Network byte order (Short)

Si vous le faites pour une autre raison, l'une des solutions byte_swap présentées ici fonctionnerait très bien.

J'ai repris quelques suggestions de cet article et les ai rassemblées pour former ceci:

#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>
#include <boost/detail/endian.hpp>
#include <stdexcept>

enum endianness
{
    little_endian,
    big_endian,
    network_endian = big_endian,

    #if defined(BOOST_LITTLE_ENDIAN)
        host_endian = little_endian
    #elif defined(BOOST_BIG_ENDIAN)
        host_endian = big_endian
    #else
        #error "unable to determine system endianness"
    #endif
};

namespace detail {

template<typename T, size_t sz>
struct swap_bytes
{
    inline T operator()(T val)
    {
        throw std::out_of_range("data size");
    }
};

template<typename T>
struct swap_bytes<T, 1>
{
    inline T operator()(T val)
    {
        return val;
    }
};

template<typename T>
struct swap_bytes<T, 2>
{
    inline T operator()(T val)
    {
        return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8));
    }
};

template<typename T>
struct swap_bytes<T, 4>
{
    inline T operator()(T val)
    {
        return ((((val) & 0xff000000) >> 24) |
                (((val) & 0x00ff0000) >>  8) |
                (((val) & 0x0000ff00) <<  8) |
                (((val) & 0x000000ff) << 24));
    }
};

template<>
struct swap_bytes<float, 4>
{
    inline float operator()(float val)
    {
        uint32_t mem =swap_bytes<uint32_t, sizeof(uint32_t)>()(*(uint32_t*)&val);
        return *(float*)&mem;
    }
};

template<typename T>
struct swap_bytes<T, 8>
{
    inline T operator()(T val)
    {
        return ((((val) & 0xff00000000000000ull) >> 56) |
                (((val) & 0x00ff000000000000ull) >> 40) |
                (((val) & 0x0000ff0000000000ull) >> 24) |
                (((val) & 0x000000ff00000000ull) >> 8 ) |
                (((val) & 0x00000000ff000000ull) << 8 ) |
                (((val) & 0x0000000000ff0000ull) << 24) |
                (((val) & 0x000000000000ff00ull) << 40) |
                (((val) & 0x00000000000000ffull) << 56));
    }
};

template<>
struct swap_bytes<double, 8>
{
    inline double operator()(double val)
    {
        uint64_t mem =swap_bytes<uint64_t, sizeof(uint64_t)>()(*(uint64_t*)&val);
        return *(double*)&mem;
    }
};

template<endianness from, endianness to, class T>
struct do_byte_swap
{
    inline T operator()(T value)
    {
        return swap_bytes<T, sizeof(T)>()(value);
    }
};
// specialisations when attempting to swap to the same endianess
template<class T> struct do_byte_swap<little_endian, little_endian, T> { inline T operator()(T value) { return value; } };
template<class T> struct do_byte_swap<big_endian,    big_endian,    T> { inline T operator()(T value) { return value; } };

} // namespace detail

template<endianness from, endianness to, class T>
inline T byte_swap(T value)
{
    // ensure the data is only 1, 2, 4 or 8 bytes
    BOOST_STATIC_ASSERT(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);
    // ensure we're only swapping arithmetic types
    BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);

    return detail::do_byte_swap<from, to, T>()(value);
}

Il existe une instruction d'assemblage appelée BSWAP qui effectuera l'échange pour vous, extrêmement rapide . Vous pouvez lire à ce sujet ici .

Visual Studio, ou plus précisément la bibliothèque d'exécution Visual C ++, possède des composants intrinsèques de la plate-forme, appelés _byteswap_ushort(), _byteswap_ulong(), and _byteswap_int64(). Il devrait en être de même pour les autres plates-formes, mais je ne sais pas comment elles s'appelleraient.

La procédure pour passer du big-endian au petit-endian est la même que pour passer du petit-endian au big-endian.

Voici un exemple de code:

void swapByteOrder(unsigned short& us)
{
    us = (us >> 8) |
         (us << 8);
}

void swapByteOrder(unsigned int& ui)
{
    ui = (ui >> 24) |
         ((ui<<8) & 0x00FF0000) |
         ((ui>>8) & 0x0000FF00) |
         (ui << 24);
}

void swapByteOrder(unsigned long long& ull)
{
    ull = (ull >> 56) |
          ((ull<<40) & 0x00FF000000000000) |
          ((ull<<24) & 0x0000FF0000000000) |
          ((ull<<8) & 0x000000FF00000000) |
          ((ull>>8) & 0x00000000FF000000) |
          ((ull>>24) & 0x0000000000FF0000) |
          ((ull>>40) & 0x000000000000FF00) |
          (ull << 56);
}

Nous l'avons fait avec des modèles. Vous pourriez avoir quelque chose comme ça:

// Specialization for 2-byte types.
template<>
inline void endian_byte_swapper< 2 >(char* dest, char const* src)
{
    // Use bit manipulations instead of accessing individual bytes from memory, much faster.
    ushort* p_dest = reinterpret_cast< ushort* >(dest);
    ushort const* const p_src = reinterpret_cast< ushort const* >(src);
    *p_dest = (*p_src >> 8) | (*p_src << 8);
}

// Specialization for 4-byte types.
template<>
inline void endian_byte_swapper< 4 >(char* dest, char const* src)
{
    // Use bit manipulations instead of accessing individual bytes from memory, much faster.
    uint* p_dest = reinterpret_cast< uint* >(dest);
    uint const* const p_src = reinterpret_cast< uint const* >(src);
    *p_dest = (*p_src >> 24) | ((*p_src & 0x00ff0000) >> 8) | ((*p_src & 0x0000ff00) << 8) | (*p_src << 24);
}

Si vous faites cela pour transférer des données entre différentes plates-formes, examinez les fonctions ntoh et hton.

Comme vous le faites dans C:

short big = 0xdead;
short little = (((big & 0xff)<<8) | ((big & 0xff00)>>8));

Vous pouvez également déclarer un vecteur de caractères non signés, mémoriser la valeur en entrée, inverser les octets en un autre vecteur et mémoriser les octets, mais cela prendra des ordres de grandeur plus longs que le twiddling, en particulier avec 64 bits. valeurs de bits.

Sur la plupart des systèmes POSIX (il n’est pas inclus dans la norme POSIX), il existe endian.h, qui peut être utilisé pour déterminer le codage utilisé par votre système. A partir de là, ça ressemble à ça:

unsigned int change_endian(unsinged int x)
{
    unsigned char *ptr = (unsigned char *)&x;
    return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
}

Ceci permute la commande (du big-endian au petit endian):

Si vous avez le numéro 0xDEADBEEF (sur un système endian enregistré sous le nom 0xEFBEADDE), ptr [0] sera 0xEF, ptr [1] sera 0xBE, etc.

Mais si vous voulez l'utiliser pour la mise en réseau, alors htons, htonl et htonll (et leurs noms inverses ntohs, ntohl et ntohll) seront utiles pour la conversion de l'ordre hôte en ordre réseau.

Notez que, du moins pour Windows, htonl () est beaucoup plus lent que son homologue intrinsèque _byteswap_ulong (). La première est un appel de bibliothèque DLL dans ws2_32.dll, la dernière est une instruction d'assemblage BSWAP. Par conséquent, si vous écrivez un code dépendant de la plate-forme, préférez utiliser les éléments intrinsèques pour la rapidité:

#define htonl(x) _byteswap_ulong(x)

Cela peut être particulièrement important pour le traitement des images .PNG où tous les entiers sont enregistrés dans le Big Endian avec l'explication & "On peut utiliser htonl () ... &"; {pour ralentir les programmes Windows typiques, si vous n’êtes pas préparé}.

La plupart des plateformes ont un fichier d’en-tête système qui fournit des fonctions efficaces byteswap. Sous Linux, c'est dans <endian.h>. Vous pouvez bien envelopper en C ++:

#include <iostream>

#include <endian.h>

template<size_t N> struct SizeT {};

#define BYTESWAPS(bits) \
template<class T> inline T htobe(T t, SizeT<bits / 8>) { return htobe ## bits(t); } \
template<class T> inline T htole(T t, SizeT<bits / 8>) { return htole ## bits(t); } \
template<class T> inline T betoh(T t, SizeT<bits / 8>) { return be ## bits ## toh(t); } \
template<class T> inline T letoh(T t, SizeT<bits / 8>) { return le ## bits ## toh(t); }

BYTESWAPS(16)
BYTESWAPS(32)
BYTESWAPS(64)

#undef BYTESWAPS

template<class T> inline T htobe(T t) { return htobe(t, SizeT<sizeof t>()); }
template<class T> inline T htole(T t) { return htole(t, SizeT<sizeof t>()); }
template<class T> inline T betoh(T t) { return betoh(t, SizeT<sizeof t>()); }
template<class T> inline T letoh(T t) { return letoh(t, SizeT<sizeof t>()); }

int main()
{
    std::cout << std::hex;
    std::cout << htobe(static_cast<unsigned short>(0xfeca)) << '\n';
    std::cout << htobe(0xafbeadde) << '\n';

    // Use ULL suffix to specify integer constant as unsigned long long 
    std::cout << htobe(0xfecaefbeafdeedfeULL) << '\n';
}

Sortie:

cafe
deadbeaf
feeddeafbeefcafe

j'aime celui-ci, juste pour le style: -)

long swap(long i) {
    char *c = (char *) &i;
    return * (long *) (char[]) {c[3], c[2], c[1], c[0] };
}

Sérieusement ... Je ne comprends pas pourquoi toutes les solutions sont aussi compliquées ! Qu'en est-il de la fonction de modèle la plus simple et la plus générale qui permute n'importe quel type ou taille, quelles que soient les circonstances et les systèmes d'exploitation?

template <typename T>
void SwapEnd(T& var)
{
    char* varArray = reinterpret_cast<char*>(&var);
    for(long i = 0; i < static_cast<long>(sizeof(var)/2); i++)
        std::swap(varArray[sizeof(var) - 1 - i],varArray[i]);
}

C’est le pouvoir magique du C et du C ++ ensemble! Échangez simplement la variable originale caractère par caractère.

N'oubliez pas que je n'ai pas utilisé l'opérateur d'affectation simple " = " parce que certains objets seront perturbés lorsque l’endianité sera retournée et que le constructeur de la copie (ou l’opérateur d’affectation) ne fonctionnera pas. Par conséquent, il est plus fiable de les copier caractère par caractère.

Pour l'appeler, utilisez simplement

double x = 5;
SwapEnd(x);

et maintenant x a une finalité différente.

J'ai ce code qui me permet de convertir HOST_ENDIAN_ORDER (quel qu'il soit) en LITTLE_ENDIAN_ORDER ou BIG_ENDIAN_ORDER. J'utilise un modèle, donc si j'essaie de convertir HOST_ENDIAN_ORDER en LITTLE_ENDIAN_ORDER et qu'ils se trouvent être identiques pour la machine pour laquelle je compile, aucun code ne sera généré.

Voici le code avec quelques commentaires:

// We define some constant for little, big and host endianess. Here I use 
// BOOST_LITTLE_ENDIAN/BOOST_BIG_ENDIAN to check the host indianess. If you
// don't want to use boost you will have to modify this part a bit.
enum EEndian
{
  LITTLE_ENDIAN_ORDER,
  BIG_ENDIAN_ORDER,
#if defined(BOOST_LITTLE_ENDIAN)
  HOST_ENDIAN_ORDER = LITTLE_ENDIAN_ORDER
#elif defined(BOOST_BIG_ENDIAN)
  HOST_ENDIAN_ORDER = BIG_ENDIAN_ORDER
#else
#error "Impossible de determiner l'indianness du systeme cible."
#endif
};

// this function swap the bytes of values given it's size as a template
// parameter (could sizeof be used?).
template <class T, unsigned int size>
inline T SwapBytes(T value)
{
  union
  {
     T value;
     char bytes[size];
  } in, out;

  in.value = value;

  for (unsigned int i = 0; i < size / 2; ++i)
  {
     out.bytes[i] = in.bytes[size - 1 - i];
     out.bytes[size - 1 - i] = in.bytes[i];
  }

  return out.value;
}

// Here is the function you will use. Again there is two compile-time assertion
// that use the boost librarie. You could probably comment them out, but if you
// do be cautious not to use this function for anything else than integers
// types. This function need to be calles like this :
//
//     int x = someValue;
//     int i = EndianSwapBytes<HOST_ENDIAN_ORDER, BIG_ENDIAN_ORDER>(x);
//
template<EEndian from, EEndian to, class T>
inline T EndianSwapBytes(T value)
{
  // A : La donnée à swapper à une taille de 2, 4 ou 8 octets
  BOOST_STATIC_ASSERT(sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);

  // A : La donnée à swapper est d'un type arithmetic
  BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);

  // Si from et to sont du même type on ne swap pas.
  if (from == to)
     return value;

  return SwapBytes<T, sizeof(T)>(value);
}

Si un entier big-endian non signé de 32 bits ressemble à 0xAABBCCDD, qui est égal à 2864434397, ce même entier non signé de 32 bits ressemble à 0xDDCCBBAA sur un processeur little-endian également égal à 2864434397.

Si un court-circuit 16 bits non signé big-endian ressemble à 0xAABB, ce qui correspond à 43707, le même court non signé 16 bits ressemble à 0xBBAA sur un processeur little-endian également égal à 43707.

Voici quelques fonctions #define pratiques permettant d’échanger des octets de little-endian en big-endian et vice-versa - >

// can be used for short, unsigned short, word, unsigned word (2-byte types)
#define BYTESWAP16(n) (((n&0xFF00)>>8)|((n&0x00FF)<<8))

// can be used for int or unsigned int or float (4-byte types)
#define BYTESWAP32(n) ((BYTESWAP16((n&0xFFFF0000)>>16))|((BYTESWAP16(n&0x0000FFFF))<<16))

// can be used for unsigned long long or double (8-byte types)
#define BYTESWAP64(n) ((BYTESWAP32((n&0xFFFFFFFF00000000)>>32))|((BYTESWAP32(n&0x00000000FFFFFFFF))<<32))

Voici une version généralisée que j’ai imaginée de prime abord pour permuter une valeur à la place. Les autres suggestions seraient meilleures si la performance posait problème.

 template<typename T>
    void ByteSwap(T * p)
    {
        for (int i = 0;  i < sizeof(T)/2;  ++i)
            std::swap(((char *)p)[i], ((char *)p)[sizeof(T)-1-i]);
    }

Avertissement: je n'ai pas encore essayé de le compiler ni de le tester.

Si vous prenez le modèle habituel pour inverser l'ordre des bits dans un mot et que vous supprimez la partie qui inverse les bits dans chaque octet, il vous reste alors quelque chose qui n'inverse que les octets dans un mot. Pour 64 bits:

x = ((x & 0x00000000ffffffff) << 32) ^ ((x >> 32) & 0x00000000ffffffff);
x = ((x & 0x0000ffff0000ffff) << 16) ^ ((x >> 16) & 0x0000ffff0000ffff);
x = ((x & 0x00ff00ff00ff00ff) <<  8) ^ ((x >>  8) & 0x00ff00ff00ff00ff);

Le compilateur devrait nettoyer les opérations superflues de masquage des bits (je les ai laissées pour mettre en surbrillance le modèle), mais si ce n'est pas le cas, vous pouvez réécrire la première ligne de cette façon:

x = ( x                       << 32) ^  (x >> 32);

Cela devrait normalement être simplifié en une seule instruction de rotation sur la plupart des architectures (en ignorant que l'opération entière est probablement une instruction).

Sur un processeur RISC, les grandes constantes compliquées peuvent causer des difficultés au compilateur. Vous pouvez cependant calculer chacune des constantes de la précédente de manière triviale. Comme si:

uint64_t k = 0x00000000ffffffff; /* compiler should know a trick for this */
x = ((x & k) << 32) ^ ((x >> 32) & k);
k ^= k << 16;
x = ((x & k) << 16) ^ ((x >> 16) & k);
k ^= k << 8;
x = ((x & k) <<  8) ^ ((x >>  8) & k);

Si vous le souhaitez, vous pouvez écrire cela en boucle. Ce ne sera pas efficace, mais juste pour le plaisir:

int i = sizeof(x) * CHAR_BIT / 2;
uintmax_t k = (1 << i) - 1;
while (i >= 8)
{
    x = ((x & k) << i) ^ ((x >> i) & k);
    i >>= 1;
    k ^= k << i;
}

Et pour compléter, voici la version 32 bits simplifiée du premier formulaire:

x = ( x               << 16) ^  (x >> 16);
x = ((x & 0x00ff00ff) <<  8) ^ ((x >>  8) & 0x00ff00ff);

Je pensais juste que j’avais ajouté ma propre solution ici puisque je ne l’ai vue nulle part. C’est une petite fonction portable basée sur un modèle C ++ et portable qui n’utilise que des opérations sur les bits.

template<typename T> inline static T swapByteOrder(const T& val) {
    int totalBytes = sizeof(val);
    T swapped = (T) 0;
    for (int i = 0; i < totalBytes; ++i) {
        swapped |= (val >> (8*(totalBytes-i-1)) & 0xFF) << (8*i);
    }
    return swapped;
}

Avec les codes donnés ci-dessous, vous pouvez facilement basculer entre BigEndian et LittleEndian

#define uint32_t unsigned 
#define uint16_t unsigned short

#define swap16(x) ((((uint16_t)(x) & 0x00ff)<<8)| \
(((uint16_t)(x) & 0xff00)>>8))

#define swap32(x) ((((uint32_t)(x) & 0x000000ff)<<24)| \
(((uint32_t)(x) & 0x0000ff00)<<8)| \
(((uint32_t)(x) & 0x00ff0000)>>8)| \
(((uint32_t)(x) & 0xff000000)>>24))

Je suis vraiment surpris que personne n'ait mentionné les fonctions htobeXX et betohXX. Elles sont définies dans endian.h et ressemblent beaucoup aux fonctions de réseau htonXX.

Wow, je ne pouvais pas croire certaines des réponses que j'ai lues ici. Il y a en fait une instruction d'assemblage qui le fait plus rapidement que toute autre chose. bswap. Vous pouvez simplement écrire une fonction comme celle-ci ...

__declspec(naked) uint32_t EndianSwap(uint32 value)
{
    __asm
    {
        mov eax, dword ptr[esp + 4]
        bswap eax
        ret
    }
}

Il est BEAUCOUP plus rapide que les éléments intrinsèques suggérés. Je les ai démontés et regardé. La fonction ci-dessus n’a pas de prologue ni d’épilogue, elle n’a donc pratiquement pas de frais généraux.

unsigned long _byteswap_ulong(unsigned long value);

Faire du 16 bits est tout aussi facile, sauf que vous utiliseriez xchg al, ah. bswap ne fonctionne que sur les registres 32 bits.

64 bits est un peu plus compliqué, mais pas trop. Beaucoup mieux que tous les exemples ci-dessus avec des boucles et des modèles, etc.

Il y a quelques réserves ici ... Premièrement, bswap n’est disponible que sur les processeurs 80x486 et supérieurs. Quelqu'un a-t-il l'intention de l'exécuter sur un 386?!? Si c'est le cas, vous pouvez toujours remplacer bswap par ...

mov ebx, eax
shr ebx, 16
xchg bl, bh
xchg al, ah
shl eax, 16
or eax, ebx

De plus, l'assemblage en ligne est uniquement disponible en code x86 dans Visual Studio. Une fonction naked ne peut pas être alignée et n'est pas non plus disponible dans les versions x64. Je cette instance, vous allez devoir utiliser les intrinsèques du compilateur.

Technique portable pour la mise en oeuvre d'accesseurs endian non inplace non alignés et conviviaux pour optimiseur Ils fonctionnent sur chaque compilateur, chaque alignement de limite et chaque ordre d'octet. Ces routines non alignées sont complétées ou suggérées, en fonction de l'endian d'origine et de l'alignement. Liste partielle mais vous avez l’idée. BO * sont des valeurs constantes basées sur l'ordre des octets natif.

uint32_t sw_get_uint32_1234(pu32)
uint32_1234 *pu32;
{
  union {
    uint32_1234 u32_1234;
    uint32_t u32;
  } bou32;
  bou32.u32_1234[0] = (*pu32)[BO32_0];
  bou32.u32_1234[1] = (*pu32)[BO32_1];
  bou32.u32_1234[2] = (*pu32)[BO32_2];
  bou32.u32_1234[3] = (*pu32)[BO32_3];
  return(bou32.u32);
}

void sw_set_uint32_1234(pu32, u32)
uint32_1234 *pu32;
uint32_t u32;
{
  union {
    uint32_1234 u32_1234;
    uint32_t u32;
  } bou32;
  bou32.u32 = u32;
  (*pu32)[BO32_0] = bou32.u32_1234[0];
  (*pu32)[BO32_1] = bou32.u32_1234[1];
  (*pu32)[BO32_2] = bou32.u32_1234[2];
  (*pu32)[BO32_3] = bou32.u32_1234[3];
}

#if HAS_SW_INT64
int64 sw_get_int64_12345678(pi64)
int64_12345678 *pi64;
{
  union {
    int64_12345678 i64_12345678;
    int64 i64;
  } boi64;
  boi64.i64_12345678[0] = (*pi64)[BO64_0];
  boi64.i64_12345678[1] = (*pi64)[BO64_1];
  boi64.i64_12345678[2] = (*pi64)[BO64_2];
  boi64.i64_12345678[3] = (*pi64)[BO64_3];
  boi64.i64_12345678[4] = (*pi64)[BO64_4];
  boi64.i64_12345678[5] = (*pi64)[BO64_5];
  boi64.i64_12345678[6] = (*pi64)[BO64_6];
  boi64.i64_12345678[7] = (*pi64)[BO64_7];
  return(boi64.i64);
}
#endif

int32_t sw_get_int32_3412(pi32)
int32_3412 *pi32;
{
  union {
    int32_3412 i32_3412;
    int32_t i32;
  } boi32;
  boi32.i32_3412[2] = (*pi32)[BO32_0];
  boi32.i32_3412[3] = (*pi32)[BO32_1];
  boi32.i32_3412[0] = (*pi32)[BO32_2];
  boi32.i32_3412[1] = (*pi32)[BO32_3];
  return(boi32.i32);
}

void sw_set_int32_3412(pi32, i32)
int32_3412 *pi32;
int32_t i32;
{
  union {
    int32_3412 i32_3412;
    int32_t i32;
  } boi32;
  boi32.i32 = i32;
  (*pi32)[BO32_0] = boi32.i32_3412[2];
  (*pi32)[BO32_1] = boi32.i32_3412[3];
  (*pi32)[BO32_2] = boi32.i32_3412[0];
  (*pi32)[BO32_3] = boi32.i32_3412[1];
}

uint32_t sw_get_uint32_3412(pu32)
uint32_3412 *pu32;
{
  union {
    uint32_3412 u32_3412;
    uint32_t u32;
  } bou32;
  bou32.u32_3412[2] = (*pu32)[BO32_0];
  bou32.u32_3412[3] = (*pu32)[BO32_1];
  bou32.u32_3412[0] = (*pu32)[BO32_2];
  bou32.u32_3412[1] = (*pu32)[BO32_3];
  return(bou32.u32);
}

void sw_set_uint32_3412(pu32, u32)
uint32_3412 *pu32;
uint32_t u32;
{
  union {
    uint32_3412 u32_3412;
    uint32_t u32;
  } bou32;
  bou32.u32 = u32;
  (*pu32)[BO32_0] = bou32.u32_3412[2];
  (*pu32)[BO32_1] = bou32.u32_3412[3];
  (*pu32)[BO32_2] = bou32.u32_3412[0];
  (*pu32)[BO32_3] = bou32.u32_3412[1];
}

float sw_get_float_1234(pf)
float_1234 *pf;
{
  union {
    float_1234 f_1234;
    float f;
  } bof;
  bof.f_1234[0] = (*pf)[BO32_0];
  bof.f_1234[1] = (*pf)[BO32_1];
  bof.f_1234[2] = (*pf)[BO32_2];
  bof.f_1234[3] = (*pf)[BO32_3];
  return(bof.f);
}

void sw_set_float_1234(pf, f)
float_1234 *pf;
float f;
{
  union {
    float_1234 f_1234;
    float f;
  } bof;
  bof.f = (float)f;
  (*pf)[BO32_0] = bof.f_1234[0];
  (*pf)[BO32_1] = bof.f_1234[1];
  (*pf)[BO32_2] = bof.f_1234[2];
  (*pf)[BO32_3] = bof.f_1234[3];
}

double sw_get_double_12345678(pd)
double_12345678 *pd;
{
  union {
    double_12345678 d_12345678;
    double d;
  } bod;
  bod.d_12345678[0] = (*pd)[BO64_0];
  bod.d_12345678[1] = (*pd)[BO64_1];
  bod.d_12345678[2] = (*pd)[BO64_2];
  bod.d_12345678[3] = (*pd)[BO64_3];
  bod.d_12345678[4] = (*pd)[BO64_4];
  bod.d_12345678[5] = (*pd)[BO64_5];
  bod.d_12345678[6] = (*pd)[BO64_6];
  bod.d_12345678[7] = (*pd)[BO64_7];
  return(bod.d);
}

void sw_set_double_12345678(pd, d)
double_12345678 *pd;
double d;
{
  union {
    double_12345678 d_12345678;
    double d;
  } bod;
  bod.d = d;
  (*pd)[BO64_0] = bod.d_12345678[0];
  (*pd)[BO64_1] = bod.d_12345678[1];
  (*pd)[BO64_2] = bod.d_12345678[2];
  (*pd)[BO64_3] = bod.d_12345678[3];
  (*pd)[BO64_4] = bod.d_12345678[4];
  (*pd)[BO64_5] = bod.d_12345678[5];
  (*pd)[BO64_6] = bod.d_12345678[6];
  (*pd)[BO64_7] = bod.d_12345678[7];
}

Ces typedefs ont l'avantage de générer des erreurs de compilation si elles ne sont pas utilisées avec des accesseurs, ce qui permet d'atténuer les bugs d'accès oubliés.

typedef char int8_1[1], uint8_1[1];

typedef char int16_12[2], uint16_12[2]; /* little endian */
typedef char int16_21[2], uint16_21[2]; /* big endian */

typedef char int24_321[3], uint24_321[3]; /* Alpha Micro, PDP-11 */

typedef char int32_1234[4], uint32_1234[4]; /* little endian */
typedef char int32_3412[4], uint32_3412[4]; /* Alpha Micro, PDP-11 */
typedef char int32_4321[4], uint32_4321[4]; /* big endian */

typedef char int64_12345678[8], uint64_12345678[8]; /* little endian */
typedef char int64_34128756[8], uint64_34128756[8]; /* Alpha Micro, PDP-11 */
typedef char int64_87654321[8], uint64_87654321[8]; /* big endian */

typedef char float_1234[4]; /* little endian */
typedef char float_3412[4]; /* Alpha Micro, PDP-11 */
typedef char float_4321[4]; /* big endian */

typedef char double_12345678[8]; /* little endian */
typedef char double_78563412[8]; /* Alpha Micro? */
typedef char double_87654321[8]; /* big endian */

J'ai récemment écrit une macro pour le faire en C, mais elle est également valable en C ++:

#define REVERSE_BYTES(...) do for(size_t REVERSE_BYTES=0; REVERSE_BYTES<sizeof(__VA_ARGS__)>>1; ++REVERSE_BYTES)\
    ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES],\
    ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES],\
    ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES];\
while(0)

Il accepte n'importe quel type et inverse les octets dans l'argument passé. Exemples d'utilisation:

int main(){
    unsigned long long x = 0xABCDEF0123456789;
    printf("Before: %llX\n",x);
    REVERSE_BYTES(x);
    printf("After : %llX\n",x);

    char c[7]="nametag";
    printf("Before: %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
    REVERSE_BYTES(c);
    printf("After : %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
}

Quelles impressions:

Before: ABCDEF0123456789
After : 8967452301EFCDAB
Before: nametag
After : gateman

Ce qui précède est parfaitement copier / coller, mais il se passe beaucoup de choses ici, je vais donc expliquer comment cela fonctionne pièce par pièce:

La première chose à noter est que la totalité de la macro est enfermée dans un bloc do while(0). Ceci est un idiome commun permettant une utilisation normale du point-virgule après la macro.

La prochaine étape consiste à utiliser une variable nommée REVERSE_BYTES en tant que compteur de la boucle for. Le nom de la macro elle-même est utilisé en tant que nom de variable pour éviter toute collision avec d'autres symboles pouvant figurer dans la portée, quelle que soit l'utilisation de la macro. Étant donné que le nom est utilisé dans le développement de la macro, il ne sera pas développé à nouveau lorsqu'il est utilisé ici comme nom de variable.

Dans la __VA_ARGS__ boucle, deux octets sont référencés et XOR échangé ( Un nom de variable temporaire n’est donc pas requis):

((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES]
((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES]

unsigned char représente tout ce qui a été donné à la macro, et est utilisé pour augmenter la flexibilité de ce qui peut être transmis (bien que pas beaucoup). L'adresse de cet argument est ensuite prise et convertie en un pointeur [] afin de permettre l'échange de ses octets via un tableau {} subscripting.

Le dernier point particulier est l’absence de <=> accolades. Elles ne sont pas nécessaires car toutes les étapes de chaque échange sont reliées à l’ opérateur de virgule , en leur faisant une déclaration.

Enfin, il convient de noter que ce n’est pas l’approche idéale si la rapidité est une priorité absolue. S'il s'agit d'un facteur important, certaines des macros spécifiques à un type ou des directives spécifiques à une plate-forme référencées dans d'autres réponses constituent probablement une meilleure option. Cette approche est toutefois portable pour tous les types, toutes les principales plates-formes, ainsi que pour les langages C et C ++.

Essayez Boost::endian et ne l'implémentez pas vous-même!

Voici un lien .

Voici comment lire un double stocké au format IEEE 754 64 bits, même si votre ordinateur hôte utilise un système différent.

/*
* read a double from a stream in ieee754 format regardless of host
*  encoding.
*  fp - the stream
*  bigendian - set to if big bytes first, clear for little bytes
*              first
*
*/
double freadieee754(FILE *fp, int bigendian)
{
    unsigned char buff[8];
    int i;
    double fnorm = 0.0;
    unsigned char temp;
    int sign;
    int exponent;
    double bitval;
    int maski, mask;
    int expbits = 11;
    int significandbits = 52;
    int shift;
    double answer;

    /* read the data */
    for (i = 0; i < 8; i++)
        buff[i] = fgetc(fp);
    /* just reverse if not big-endian*/
    if (!bigendian)
    {
        for (i = 0; i < 4; i++)
        {
            temp = buff[i];
            buff[i] = buff[8 - i - 1];
            buff[8 - i - 1] = temp;
        }
    }
    sign = buff[0] & 0x80 ? -1 : 1;
    /* exponet in raw format*/
    exponent = ((buff[0] & 0x7F) << 4) | ((buff[1] & 0xF0) >> 4);

    /* read inthe mantissa. Top bit is 0.5, the successive bits half*/
    bitval = 0.5;
    maski = 1;
    mask = 0x08;
    for (i = 0; i < significandbits; i++)
    {
        if (buff[maski] & mask)
            fnorm += bitval;

        bitval /= 2.0;
        mask >>= 1;
        if (mask == 0)
        {
            mask = 0x80;
            maski++;
        }
    }
    /* handle zero specially */
    if (exponent == 0 && fnorm == 0)
        return 0.0;

    shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */
    /* nans have exp 1024 and non-zero mantissa */
    if (shift == 1024 && fnorm != 0)
        return sqrt(-1.0);
    /*infinity*/
    if (shift == 1024 && fnorm == 0)
    {

#ifdef INFINITY
        return sign == 1 ? INFINITY : -INFINITY;
#endif
        return  (sign * 1.0) / 0.0;
    }
    if (shift > -1023)
    {
        answer = ldexp(fnorm + 1.0, shift);
        return answer * sign;
    }
    else
    {
        /* denormalised numbers */
        if (fnorm == 0.0)
            return 0.0;
        shift = -1022;
        while (fnorm < 1.0)
        {
            fnorm *= 2;
            shift--;
        }
        answer = ldexp(fnorm, shift);
        return answer * sign;
    }
}

Pour le reste de la suite de fonctions, y compris les routines d'écriture et d'entier, voir mon projet github

https://github.com/MalcolmMcLean/ieee754

L'échange d'octets avec votre ancienne méthode en trois étapes autour d'un pivot dans une fonction de modèle fournit une solution flexible et rapide O (ln2) qui ne nécessite pas de bibliothèque, le style rejette également les types à 1 octet:

template<typename T>void swap(T &t){
    for(uint8_t pivot = 0; pivot < sizeof(t)/2; pivot ++){
        *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
        *((uint8_t *)&t+sizeof(t)-1- pivot) ^= *((uint8_t *)&t + pivot);
        *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
    }
}

On dirait que le moyen le plus sûr serait d’utiliser htons sur chaque mot. Donc, si vous avez ...

std::vector<uint16_t> storage(n);  // where n is the number to be converted

// the following would do the trick
std::transform(word_storage.cbegin(), word_storage.cend()
  , word_storage.begin(), [](const uint16_t input)->uint16_t {
  return htons(input); });

Ce qui précède serait un no-op si vous utilisiez un système big-endian. Je rechercherais donc tout ce que votre plateforme utilisera comme condition de compilation pour décider si htons est un no-op. C'est O (n) après tout. Sur un Mac, ce serait quelque chose comme ...

#if (__DARWIN_BYTE_ORDER != __DARWIN_BIG_ENDIAN)
std::transform(word_storage.cbegin(), word_storage.cend()
  , word_storage.begin(), [](const uint16_t input)->uint16_t {
  return htons(input); });
#endif

Regardez le décalage, car c’est tout ce que vous avez à faire pour changer de petit - > Big Endian. Ensuite, en fonction de la taille du bit, vous modifiez la façon dont vous effectuez le décalage de bit.

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