Comment convertir des valeurs big-endian et little-endian en C++?
EDIT: Par souci de clarté, je dois traduire des données binaires (valeurs à virgule flottante double précision et nombres 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 portables.
Si vous utilisez Visual C++ effectuez les opérations suivantes: 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.
En outre, 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 qu'avec les entiers simples, car ceux-ci peuvent ou non être dans l'ordre des octets des machines hôtes. 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.
DansGCCpar 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.
16 bits swap c'est juste un peu de rotation.
Appeler les éléments intrinsèques au lieu de vous lancer vous donne la meilleure performance et densité de code, en fait.
Tout simplement:
#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 The Byte Order Fallacy 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: il a été remarqué dans le commentaire qu'en l'absence de conversion de type explicite, il était important que data
soit un tableau de unsigned char
ou uint8_t
. L'utilisation de signed char
ou char
(si elle est signée) entraînera la promotion de data[x]
en un entier et de data[x] << 24
potentiellement décaler un 1 dans le bit de signe qui est UB.
Si vous le faites 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 fonctionnera parfaitement.
J'ai pris quelques suggestions de ce post 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 Assembly appelée BSWAP qui effectuera l’échange pour vous extrêmement rapide . Vous pouvez en lire plus ici .
Visual Studio, ou plus précisément la bibliothèque d'exécution Visual C++, possède pour cela des éléments 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 celle 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 tellement 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 en 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 les valeurs 64 bits.
Sur la plupart des systèmes POSIX (via le standard POSIX en particulier), il existe le fichier 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 (de big-endian à little endian):
Si vous avez le numéro 0xDEADBEEF (sur un système little endian stocké sous 0xEFBEADDE), ptr [0] sera 0xEF, ptr [1] est 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 de l'hôte en ordre du 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 Big Endian avec l'explication "On peut utiliser htonl () ..." {pour ralentir les programmes Windows typiques, si vous n'êtes pas préparé}.
Sérieusement ... Je ne comprends pas pourquoi toutes les solutions sont quecompliqué! Que diriez-vous de la fonction de modèle la plus simple et la plus générale qui permute n'importe quel type de 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 d'origine caractère par caractère.
Rappelez-vous que je n'ai pas utilisé l'opérateur d'affectation simple "=" car certains objets seront perturbés lorsque la finalité est inversée et que le constructeur de la copie (ou l'opérateur d'affectation) ne fonctionne pas. Par conséquent, il est plus fiable de les copier caractère par caractère.
Pour l'appeler, il suffit d'utiliser
double x = 5;
SwapEnd(x);
et maintenant x
a une finalité différente.
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] };
}
La plupart des plates-formes 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
Si un entier non signé 32 bits big-endian ressemble à 0xAABBCCDD et est égal à 2864434397, ce même entier non signé 32 bits ressemble à 0xDDCCBBAA sur un processeur little-endian également égal à 2864434397.
Si un short non signé 16 bits 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 pour permuter les 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))
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'essaye de convertir Host_ENDIAN_ORDER en LITTLE_ENDIAN_ORDER et qu'ils se trouvent être les mêmes 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);
}
Je pensais juste que j’avais ajouté ma propre solution ici puisque je ne l’ai vue nulle part. Il s'agit d'une petite fonction portable basée sur un modèle C++ et portable qui utilise uniquement des opérations sur 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;
}
Voici une version généralisée que j’ai imaginée de prime abord pour permuter une valeur sur 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, vous vous retrouvez avec 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 nettoie les opérations de masquage de bits superflues (je les ai laissées pour mettre en évidence le motif), 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 toutefois calculer chacune des constantes de la précédente de manière triviale. Ainsi:
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 suis vraiment surpris que personne n'ait mentionné les fonctions htobeXX et betohXX. Elles sont définies dans endian.h et sont très similaires aux fonctions de réseau htonXX.
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))
Il semble que le moyen le plus sûr serait d'utiliser des 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 non-fonctionnement si vous utilisiez un système big-endian. Je chercherais donc ce que votre plate-forme utilise comme condition de compilation pour décider si htons est un non-opérateur. 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
Technique portable pour la mise en oeuvre d'accesseurs endian non inplace non alignés et conviviaux pour les optimiseurs. 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 de limiter 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'utilisations:
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 macro entière est encapsulée dans un bloc do while(0)
. Ceci est un idiome commun pour permettre une utilisation normale du point-virgule après la macro.
La prochaine étape consiste à utiliser une variable nommée REVERSE_BYTES
comme 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, où que la macro soit utilisée. Comme le nom est utilisé dans le développement de la macro, il ne sera pas développé à nouveau s'il est utilisé comme nom de variable ici.
Dans la boucle for
, il y a deux octets 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]
__VA_ARGS__
représente tout ce qui a été donné à la macro et est utilisé pour augmenter la flexibilité de ce qui peut être transmis (même si ce n'est pas beaucoup). L'adresse de cet argument est ensuite prise et convertie en un pointeur unsigned char
afin de permettre la permutation de ses octets via un tableau []
souscrit.
Le dernier point particulier est l’absence d’accolades {}
. Elles ne sont pas nécessaires car toutes les étapes de chaque échange sont reliées à l'opérateur virgule , ce qui les rend une déclaration.
Enfin, il convient de noter que cette approche n’est pas idéale si la rapidité est une priorité absolue. S'il s'agit d'un facteur important, certaines des macros spécifiques au type ou des directives spécifiques à la 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 et les langages C et C++.
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 write et integer, voir mon projet github
L'échange d'octets avec votre vieille astuce en trois étapes autour d'un pivot dans une fonction de modèle fournit une solution rapide et flexible O(ln2) ne nécessitant 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);
}
}
Essayez Boost::endian
et ne l'implémentez pas vous-même!
Voici un link
Wow, je ne pouvais pas croire certaines des réponses que j'ai lues ici. Il y a en fait une instruction dans Assembly 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 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/épilogue et n'a donc pratiquement aucun surcoût.
unsigned long _byteswap_ulong(unsigned long value);
Faire 16 bits est tout aussi facile, excepté 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 est uniquement disponible sur les processeurs 80x486 et supérieurs. Quelqu'un a-t-il l'intention de l'exécuter sur un 386?!? Si oui, 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. Dans ce cas, vous devrez utiliser les éléments intrinsèques du compilateur.