Comment convertir les valeurs big-endian en C++?
comment convertir entre les valeurs big-endian et little-endian en C++?
EDIT: pour plus de clarté, je dois traduire des données binaires (valeurs à virgule flottante de double précision et nombres entiers de 32 et 64 bits) d'une architecture CPU à une autre. Cela n'implique pas la mise en 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 y a d'autres réponses très bonnes, Plus portables ici.
28 réponses
si vous utilisez Visual C++ faites ce qui suit: vous incluez intrin.h et appeler les fonctions suivantes:
pour les numéros à 16 bits:
unsigned short _byteswap_ushort(unsigned short value);
pour les numéros 32 bits:
unsigned long _byteswap_ulong(unsigned long value);
pour les numéros à 64 bits:
unsigned __int64 _byteswap_uint64(unsigned __int64 value);
Les numéros de 8 bits n'ont pas besoin d'être convertis.
aussi ceux-ci sont seulement définis pour les valeurs non signées qu'ils travaillent pour signé les entiers.
pour flotteurs et doubles il est plus difficile comme avec des entiers simples que ceux-ci peuvent ou non ÊTRE dans les machines hôtes byte-ordre. Vous pouvez obtenir des flotteurs little-endian sur des machines big-endian et vice versa.
D'autres compilateurs ont des intrinsèques similaires aussi bien.
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). Autant que je sache bit.h déclare également la même fonction d'une manière non centrée sur le gcc.
swap 16 bits c'est juste un peu-rotation.
appeler les intrinsèques au lieu de rouler les vôtres vous donne la meilleure performance et la densité de code btw..
simplement dit:
#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)
.
à Partir de L'Ordre des Octets de Sophisme par Rob Pyke:
disons que votre flux de données a un entier de 32 bits encodé little-endian. Voici comment l'extraire (en supposant 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: don't souciez-vous de votre ordre natif de plate-forme, tout ce qui compte est l'ordre de byte du flux que vous lisez à partir, et vous feriez mieux d'espérer qu'il est bien défini.
Note: il a été noté 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 signé) se traduira par data[x]
être promu à un entier et data[x] << 24
potentiellement déplacer un 1 dans le signe bit qui est UB.
si vous faites cela à des fins 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 faites cela pour une autre raison, une des solutions byte_swap présentées ici fonctionnerait très bien.
j'ai pris quelques suggestions de ce post et les ai mis ensemble 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 y a une instruction d'assemblage appelée bswap qui fera l'échange pour vous, extrêmement rapide . Vous pouvez le lire ici .
Visual Studio, ou plus précisément la Visual C++ runtime library, a une plate-forme intrinsèque pour cela, appelée _byteswap_ushort(), _byteswap_ulong(), and _byteswap_int64()
. Il devrait en être de même pour les autres plateformes, mais je ne sais pas comment on les appellerait.
La procédure de big-endian pour little-endian est le même que d'aller de little-endian pour 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 avons fait cela avec des gabarits. Vous pourriez donc quelque chose comme ceci:
// 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 plateformes regardez les fonctions ntoh et hton.
comme 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, memcpy la valeur d'entrée, inverser les octets dans un autre vecteur et memcpy les octets out, mais qui va prendre des ordres de grandeur plus long que bit-twiddling, en particulier avec des valeurs de 64 bits.
sur la plupart des systèmes POSIX (ce n'est pas dans le standard POSIX) il y a l'endian.h, qui peut être utilisé pour déterminer l'encodage que votre système utilise. De là, il y a quelque chose comme ceci:
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 échange l'ordre (de big-endian à little endian):
si vous avez le nombre 0xDEADBEEF (sur un petit système endian stocké comme 0xEFBEADDE), ptr[0] sera 0xEF, ptr[1] est 0xBE, etc.
Mais si vous voulez utilisez - le pour la mise en réseau, puis htons, htonl et htonll (et leurs ntohs inverses, ntohl et ntohll) seront utiles pour passer de l'ordre hôte à l'ordre réseau.
notez que, au moins pour Windows, htonl() est beaucoup plus lent que son homologue intrinsèque _byteswap_ulong(). Le premier est un appel DLL dans ws2_32.dll, ce dernier 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 intrinsèques pour la vitesse:
#define htonl(x) _byteswap_ulong(x)
Cela peut être particulièrement important pour .Traitement D'image PNG où tous les entiers sont enregistrés en Big Endian avec explication "on peut utiliser htonl ()..."{ralentir typique de programmes de Windows, si vous n'êtes pas préparé}.
la plupart des plates-formes ont un fichier d'en-tête de système qui fournit des fonctions byteswap efficaces. Sur Linux, c'est dans <endian.h>
. Vous pouvez l'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
je l'aime, 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 que compliqué ! Qu'en est-il de la fonction de modèle la plus simple et la plus générale qui échange n'importe quel type de n'importe quelle taille dans n'importe quelles circonstances dans n'importe quel système 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 de C et C++ ensemble! Il suffit d'échanger le caractère original de la variable par caractère.
rappelez - vous que je n'ai pas utilisez l'opérateur d'affectation simple " = " parce que certains objets seront abîmés lorsque l'endianness est inversé et que le constructeur de copie (ou l'opérateur d'affectation) ne fonctionnera pas. Par conséquent, il est plus fiable de les copier char par char.
Pour l'appeler, il suffit d'utiliser
double x = 5;
SwapEnd(x);
et maintenant x
est différent dans l'ennui.
j'ai ce code qui me permet de passer de HOST_ENDIAN_ORDER (quel qu'il soit) à LITTLE_ENDIAN_ORDER ou BIG_ENDIAN_ORDER. J'utilise un modèle, donc si j'essaie de convertir de HOST_ENDIAN_ORDER en LITTLE_ENDIAN_ORDER et qu'il se trouve qu'ils sont les mêmes pour la machine pour laquelle je compilais, 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 non signé de 32 bits ressemble à 0xaabbccdd qui est égal à 2864434397, alors ce même entier non signé de 32 bits ressemble à 0xDDCCBBAA sur un processeur de little-endian qui est également égal à 2864434397.
si un short 16 bits non signé ressemble à 0xaabb qui est égal à 43707, alors ce même short 16 bits non signé ressemble à 0xBBAA sur un processeur little-endian qui est également égal à 43707.
voici un couple de maniable #définir des fonctions pour échanger des octets de little-endian à 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 trouvée sur le dessus de ma tête, pour échanger une valeur en place. Les autres suggestions seraient meilleures si la performance est un 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 essayé de compiler ce ou de le tester encore.
si vous prenez le modèle commun pour inverser l'ordre des bits dans un mot, et cull la partie qui inverse les bits dans chaque octet, alors vous êtes laissés avec quelque chose qui inverse seulement 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 de bits (je les ai laissés 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);
qui devrait normalement simplifier jusqu'à une seule instruction de rotation sur la plupart des architectures (en ignorant que toute l'opération est probablement une instruction).
sur un processeur RISC les grandes constantes compliquées peuvent causer des difficultés de compilateur. Vous pouvez calculer trivialement chacune des constantes de la précédente, cependant. Comme ceci:
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 voulez, vous pouvez l'écrire en boucle. Il 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 être complet, voici la version 32 bits simplifiée du premier formulaire:
x = ( x << 16) ^ (x >> 16);
x = ((x & 0x00ff00ff) << 8) ^ ((x >> 8) & 0x00ff00ff);
juste pensé que j'ai ajouté ma propre solution ici puisque je ne l'ai vu nulle part. C'est une petite fonction C++ templated et portable qui n'utilise que des opérations de 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 échanger 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. Ils sont définis en endian.h et sont très similaires aux fonctions 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 de montage qui fait ça plus vite que tout le reste. 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 vite que la intrinsèques qui ont été proposées. J'ai démonté et regarda. La fonction ci-dessus n'a pas de prologue/épilogue donc pratiquement pas de frais généraux du tout.
unsigned long _byteswap_ulong(unsigned long value);
faire 16 bits est tout aussi facile, à l'exception que vous utiliseriez xchg al, ah. bswap ne fonctionne que sur des registres 32 bits.
64-bit est un peu plus délicat, mais pas trop. Beaucoup mieux que tous les exemples ci-dessus avec des boucles et des modèles etc.
il y a quelques mises en garde ici... Tout d'abord, bswap n'est disponible que sur les CPU 80x486 et supérieurs. Est-ce que quelqu'un prévoit de le faire tourner 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
aussi montage en ligne n'est disponible qu'en code x86 Dans Visual Studio. Une fonction nue ne peut pas être doublée et n'est pas non plus disponible dans les constructions x64. Dans ce cas, vous devrez utiliser le compilateur intrinsics.
Portable technique pour la mise en œuvre de l'assistant convivial non alignés non-inplace endian accesseurs. Ils travaillent sur chaque compilateur, chaque alignement de limite et chaque ordre de byte. Ces routines non alignées sont complétées, ou suggérées, en fonction de l'endian natif et de l'alignement. Liste partielle mais vous obtenez l'idée. BO* sont des valeurs constantes basées sur l'ordre des octets natifs.
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 d'augmenter les erreurs de compilation si elles ne sont pas utilisées avec accesseurs, ce qui atténue les bogues d'accesseurs 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 faire ceci en C, mais elle est également valide 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]);
}
qui imprime:
Before: ABCDEF0123456789
After : 8967452301EFCDAB
Before: nametag
After : gateman
ce qui précède est parfaitement copiable / collable, mais il y a beaucoup de choses qui se passent ici, donc je vais détailler comment cela fonctionne pièce par pièce:
la première chose notable est que la macro entière est enfermée dans un bloc do while(0)
. Il s'agit d'un idiome commun pour permettre une utilisation normale du point-virgule après la macro.
vient ensuite l'utilisation d'une variable appelée REVERSE_BYTES
comme compteur de la boucle for
. Le nom de la macro elle-même est utilisé comme nom de variable pour s'assurer qu'elle n'entre pas en conflit avec d'autres symboles qui peuvent être dans scope partout où la macro est utilisée. Puisque le nom est utilisé dans l'expansion de la macro, il ne sera pas être élargi à nouveau lorsqu'il est utilisé comme un nom de variable.
dans la boucle for
, il y a deux octets référencés et XOR swapped (donc un nom de variable temporaire n'est 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 passé dans (bien que pas de beaucoup). L'adresse de cet argument est alors prise et coulée sur un pointeur unsigned char
pour permettre l'échange de ses octets via le tableau []
sous-titrage.
le dernier point particulier est l'absence de bretelles {}
. Ils ne sont pas nécessaires parce que toutes les étapes de chaque swap sont jointes avec le opérateur virgule , ce qui leur fait une déclaration.
Enfin, il est intéressant de noter que ce n'est pas l'approche idéale si la vitesse est une priorité absolue. Si c'est un facteur important, certaines des macros spécifiques au type ou des directives spécifiques à la plate-forme citées dans d'autres réponses sont probablement une meilleure option. Cette approche, cependant, est portable pour tous les types, toutes les plates-formes majeures et les langages C et C++.
Voici comment lire un double stocké dans le 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
regarder en haut bit shifting, car c'est essentiellement tout ce que vous devez faire pour échanger de little -> big endian. Ensuite, en fonction de la taille du bit, vous changez la façon de faire le changement de bit.