Les pages de manuel de htonl()
semblent suggérer que vous ne pouvez l'utiliser que pour des valeurs jusqu'à 32 bits. (En réalité, ntohl()
est défini pour les longs non signés, qui sur ma plate-forme est de 32 bits. Je suppose que si les longs non signés étaient de 8 octets, cela fonctionnerait pour des entiers de 64 bits).
Mon problème est que je dois convertir des entiers 64 bits (dans mon cas, c'est un long long non signé) de big endian en little endian. En ce moment, je dois faire cette conversion spécifique. Mais ce serait encore plus agréable si la fonction (comme ntohl()
) ne convertissait PAS ma valeur 64 bits si la plate-forme cible était gros endian. (Je préfère éviter d'ajouter ma propre magie de préprocesseur pour ce faire).
Que puis-je utiliser? Je voudrais quelque chose de standard s'il existe, mais je suis ouvert aux suggestions de mise en œuvre. J'ai vu ce type de conversion effectué dans le passé en utilisant des syndicats. Je suppose que je pourrais avoir une union avec un long long non signé et un char [8]. Échangez ensuite les octets en conséquence. (De toute évidence, il se briserait sur des plates-formes qui étaient gros endian).
Documentation: man htobe64
sous Linux (glibc> = 2.9) ou FreeBSD.
Malheureusement, OpenBSD, FreeBSD et glibc (Linux) ne se sont pas tout à fait harmonisés pour créer une norme libc (non-kernel-API) pour cela, lors d'une tentative en 2009.
Actuellement, ce petit bout de code de préprocesseur:
#if defined(__linux__)
# include <endian.h>
#Elif defined(__FreeBSD__) || defined(__NetBSD__)
# include <sys/endian.h>
#Elif defined(__OpenBSD__)
# include <sys/types.h>
# define be16toh(x) betoh16(x)
# define be32toh(x) betoh32(x)
# define be64toh(x) betoh64(x)
#endif
(testé sur Linux et OpenBSD) devrait masquer les différences. Il vous donne les macros de style Linux/FreeBSD sur ces 4 plateformes.
Exemple d'utilisation:
#include <stdint.h> // For 'uint64_t'
uint64_t Host_int = 123;
uint64_t big_endian;
big_endian = htobe64( Host_int );
Host_int = be64toh( big_endian );
C'est l'approche la plus "bibliothèque C standard" disponible actuellement.
Je recommanderais de lire ceci: http://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
uint64_t
ntoh64(const uint64_t *input)
{
uint64_t rval;
uint8_t *data = (uint8_t *)&rval;
data[0] = *input >> 56;
data[1] = *input >> 48;
data[2] = *input >> 40;
data[3] = *input >> 32;
data[4] = *input >> 24;
data[5] = *input >> 16;
data[6] = *input >> 8;
data[7] = *input >> 0;
return rval;
}
uint64_t
hton64(const uint64_t *input)
{
return (ntoh64(input));
}
int
main(void)
{
uint64_t ull;
ull = 1;
printf("%"PRIu64"\n", ull);
ull = ntoh64(&ull);
printf("%"PRIu64"\n", ull);
ull = hton64(&ull);
printf("%"PRIu64"\n", ull);
return 0;
}
Affiche la sortie suivante:
1
72057594037927936
1
Vous pouvez tester cela avec ntohl () si vous supprimez les 4 octets supérieurs.
Vous pouvez également transformer cela en une fonction de modèle Nice en C++ qui fonctionnera sur n'importe quel entier de taille:
template <typename T>
static inline T
hton_any(const T &input)
{
T output(0);
const std::size_t size = sizeof(input);
uint8_t *data = reinterpret_cast<uint8_t *>(&output);
for (std::size_t i = 0; i < size; i++) {
data[i] = input >> ((size - i - 1) * 8);
}
return output;
}
Maintenant, votre 128 bits en sécurité aussi!
Pour détecter votre endianité, utilisez l'union suivante:
union {
unsigned long long ull;
char c[8];
} x;
x.ull = 0x0123456789abcdef; // may need special suffix for ULL.
Ensuite, vous pouvez vérifier le contenu de x.c[]
Pour détecter où chaque octet est allé.
Pour effectuer la conversion, j'utiliserais ce code de détection une fois pour voir quelle finesse la plate-forme utilise, puis j'écrirais ma propre fonction pour effectuer les échanges.
Vous pouvez le rendre dynamique pour que le code s'exécute sur n'importe quelle plate-forme (détecter une fois puis utiliser un commutateur dans votre code de conversion pour choisir la bonne conversion) mais, si vous n'utilisez qu'une seule plate-forme, je le ferais la détection une fois dans un programme séparé, puis codez une routine de conversion simple, en vous assurant de documenter qu'elle ne fonctionne (ou n'a été testée) que sur cette plate-forme.
Voici un exemple de code que j'ai fouetté pour l'illustrer. Il a été testé mais pas de manière approfondie, mais devrait être suffisant pour vous aider à démarrer.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TYP_INIT 0
#define TYP_SMLE 1
#define TYP_BIGE 2
static unsigned long long cvt(unsigned long long src) {
static int typ = TYP_INIT;
unsigned char c;
union {
unsigned long long ull;
unsigned char c[8];
} x;
if (typ == TYP_INIT) {
x.ull = 0x01;
typ = (x.c[7] == 0x01) ? TYP_BIGE : TYP_SMLE;
}
if (typ == TYP_SMLE)
return src;
x.ull = src;
c = x.c[0]; x.c[0] = x.c[7]; x.c[7] = c;
c = x.c[1]; x.c[1] = x.c[6]; x.c[6] = c;
c = x.c[2]; x.c[2] = x.c[5]; x.c[5] = c;
c = x.c[3]; x.c[3] = x.c[4]; x.c[4] = c;
return x.ull;
}
int main (void) {
unsigned long long ull = 1;
ull = cvt (ull);
printf ("%llu\n",ull);
return 0;
}
Gardez à l'esprit que cela vérifie simplement le gros/petit endian pur. Si vous avez une variante étrange où les octets sont stockés dans, par exemple, {5,2,3,1,0,7,6,4} ordre, cvt()
sera un peu plus complexe. Une telle architecture ne mérite pas d'exister, mais je n'écarte pas la folie de nos amis de l'industrie des microprocesseurs :-)
Gardez également à l'esprit qu'il s'agit d'un comportement techniquement indéfini, car vous n'êtes pas censé accéder à un membre de l'union par un champ autre que le dernier écrit. Cela fonctionnera probablement avec la plupart des implémentations mais, du point de vue puriste, vous devriez probablement mordre la balle et utiliser des macros pour définir vos propres routines, quelque chose comme:
// Assumes 64-bit unsigned long long.
unsigned long long switchOrderFn (unsigned long long in) {
in = (in && 0xff00000000000000ULL) >> 56
| (in && 0x00ff000000000000ULL) >> 40
| (in && 0x0000ff0000000000ULL) >> 24
| (in && 0x000000ff00000000ULL) >> 8
| (in && 0x00000000ff000000ULL) << 8
| (in && 0x0000000000ff0000ULL) << 24
| (in && 0x000000000000ff00ULL) << 40
| (in && 0x00000000000000ffULL) << 56;
return in;
}
#ifdef ULONG_IS_NET_ORDER
#define switchOrder(n) (n)
#else
#define switchOrder(n) switchOrderFn(n)
#endif
certains systèmes BSD ont betoh64
qui fait ce dont vous avez besoin.
#include <endian.h> // __BYTE_ORDER __LITTLE_ENDIAN
#include <byteswap.h> // bswap_64()
uint64_t value = 0x1122334455667788;
#if __BYTE_ORDER == __LITTLE_ENDIAN
value = bswap_64(value); // Compiler builtin GCC/Clang
#endif
Comme indiqué par zhaorufei (voir son commentaire) endian.h
N'est pas un en-tête standard C++ et les macros __BYTE_ORDER
Et __LITTLE_ENDIAN
Peuvent ne pas être définies. Par conséquent, l'instruction #if
N'est pas prévisible car les macros non définies sont traitées comme 0
.
Veuillez modifier cette réponse si vous souhaitez partager votre astuce élégante C++ pour détecter l'endianité.
De plus, la macro bswap_64()
est disponible pour les compilateurs GCC et Clang mais pas pour le compilateur Visual C++. Pour fournir un code source portable, vous pouvez être inspiré par l'extrait de code suivant:
#ifdef _MSC_VER
#include <stdlib.h>
#define bswap_16(x) _byteswap_ushort(x)
#define bswap_32(x) _byteswap_ulong(x)
#define bswap_64(x) _byteswap_uint64(x)
#else
#include <byteswap.h> // bswap_16 bswap_32 bswap_64
#endif
Voir aussi un code source plus portable: multiplateforme _byteswap_uint64
constexpr
fonction de modèleGénérique hton()
pour 16 bits, 32 bits, 64 bits et plus ...
#include <endian.h> // __BYTE_ORDER __LITTLE_ENDIAN
#include <algorithm> // std::reverse()
template <typename T>
constexpr T htonT (T value) noexcept
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
char* ptr = reinterpret_cast<char*>(&value);
std::reverse(ptr, ptr + sizeof(T));
#endif
return value;
}
constexpr
fonction de modèleconstexpr
.constexpr
doit contenir une seule expression.template <typename T>
constexpr T htonT (T value, char* ptr=0) noexcept
{
return
#if __BYTE_ORDER == __LITTLE_ENDIAN
ptr = reinterpret_cast<char*>(&value),
std::reverse(ptr, ptr + sizeof(T)),
#endif
value;
}
Aucun avertissement de compilation sur clang-3.5 et GCC-4.9 en utilisant -Wall -Wextra -pedantic
(voir compilation et exécution de la sortie sur colir ).
constexpr
modèle fonctions SFINAECependant, la version ci-dessus ne permet pas de créer la variable constexpr
comme:
constexpr int32_t hton_six = htonT( int32_t(6) );
Enfin, nous devons séparer (spécialiser) les fonctions en fonction de 16/32/64 bits.
Mais nous pouvons toujours conserver des fonctions génériques.
(voir l'extrait complet sur colir )
L'extrait ci-dessous C++ 11 utilise les traitsstd::enable_if
pour exploiter L'échec de substitution n'est pas une erreur (SFINAE).
template <typename T>
constexpr typename std::enable_if<sizeof(T) == 2, T>::type
htonT (T value) noexcept
{
return ((value & 0x00FF) << 8)
| ((value & 0xFF00) >> 8);
}
template <typename T>
constexpr typename std::enable_if<sizeof(T) == 4, T>::type
htonT (T value) noexcept
{
return ((value & 0x000000FF) << 24)
| ((value & 0x0000FF00) << 8)
| ((value & 0x00FF0000) >> 8)
| ((value & 0xFF000000) >> 24);
}
template <typename T>
constexpr typename std::enable_if<sizeof(T) == 8, T>::type
htonT (T value) noexcept
{
return ((value & 0xFF00000000000000ull) >> 56)
| ((value & 0x00FF000000000000ull) >> 40)
| ((value & 0x0000FF0000000000ull) >> 24)
| ((value & 0x000000FF00000000ull) >> 8)
| ((value & 0x00000000FF000000ull) << 8)
| ((value & 0x0000000000FF0000ull) << 24)
| ((value & 0x000000000000FF00ull) << 40)
| ((value & 0x00000000000000FFull) << 56);
}
Ou une version encore plus courte basée sur des macros de compilateur intégrées et la syntaxe C++ 14 std::enable_if_t<xxx>
Comme raccourci pour std::enable_if<xxx>::type
:
template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 2, T>
htonT (T value) noexcept
{
return bswap_16(value); // __bswap_constant_16
}
template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 4, T>
htonT (T value) noexcept
{
return bswap_32(value); // __bswap_constant_32
}
template <typename T>
constexpr typename std::enable_if_t<sizeof(T) == 8, T>
htonT (T value) noexcept
{
return bswap_64(value); // __bswap_constant_64
}
std::uint8_t uc = 'B'; std::cout <<std::setw(16)<< uc <<'\n';
uc = htonT( uc ); std::cout <<std::setw(16)<< uc <<'\n';
std::uint16_t us = 0x1122; std::cout <<std::setw(16)<< us <<'\n';
us = htonT( us ); std::cout <<std::setw(16)<< us <<'\n';
std::uint32_t ul = 0x11223344; std::cout <<std::setw(16)<< ul <<'\n';
ul = htonT( ul ); std::cout <<std::setw(16)<< ul <<'\n';
std::uint64_t uL = 0x1122334455667788; std::cout <<std::setw(16)<< uL <<'\n';
uL = htonT( uL ); std::cout <<std::setw(16)<< uL <<'\n';
constexpr uint8_t a1 = 'B'; std::cout<<std::setw(16)<<a1<<'\n';
constexpr auto b1 = htonT(a1); std::cout<<std::setw(16)<<b1<<'\n';
constexpr uint16_t a2 = 0x1122; std::cout<<std::setw(16)<<a2<<'\n';
constexpr auto b2 = htonT(a2); std::cout<<std::setw(16)<<b2<<'\n';
constexpr uint32_t a4 = 0x11223344; std::cout<<std::setw(16)<<a4<<'\n';
constexpr auto b4 = htonT(a4); std::cout<<std::setw(16)<<b4<<'\n';
constexpr uint64_t a8 = 0x1122334455667788;std::cout<<std::setw(16)<<a8<<'\n';
constexpr auto b8 = htonT(a8); std::cout<<std::setw(16)<<b8<<'\n';
B
B
1122
2211
11223344
44332211
1122334455667788
8877665544332211
Le compilateur C++ en ligne gcc.godbolt.org indique le code généré.
g++-4.9.2 -std=c++14 -O3
std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char):
movl %edi, %eax
ret
std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short):
movl %edi, %eax
rolw $8, %ax
ret
std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int):
movl %edi, %eax
bswap %eax
ret
std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long):
movq %rdi, %rax
bswap %rax
ret
clang++-3.5.1 -std=c++14 -O3
std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char): # @std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT<unsigned char>(unsigned char)
movl %edi, %eax
retq
std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short): # @std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT<unsigned short>(unsigned short)
rolw $8, %di
movzwl %di, %eax
retq
std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int): # @std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT<unsigned int>(unsigned int)
bswapl %edi
movl %edi, %eax
retq
std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long): # @std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT<unsigned long>(unsigned long)
bswapq %rdi
movq %rdi, %rax
retq
Remarque: mon réponse d'origine n'était pas conforme à C++ 11 -constexpr
.
Cette réponse est dans Public Domain CC0 1.0 Universal
macro d'une ligne pour un échange 64 bits sur de petites machines endiennes.
#define bswap64(y) (((uint64_t)ntohl(y)) << 32 | ntohl(y>>32))
uint32_t SwapShort(uint16_t a)
{
a = ((a & 0x00FF) << 8) | ((a & 0xFF00) >> 8);
return a;
}
uint32_t SwapWord(uint32_t a)
{
a = ((a & 0x000000FF) << 24) |
((a & 0x0000FF00) << 8) |
((a & 0x00FF0000) >> 8) |
((a & 0xFF000000) >> 24);
return a;
}
uint64_t SwapDWord(uint64_t a)
{
a = ((a & 0x00000000000000FFULL) << 56) |
((a & 0x000000000000FF00ULL) << 40) |
((a & 0x0000000000FF0000ULL) << 24) |
((a & 0x00000000FF000000ULL) << 8) |
((a & 0x000000FF00000000ULL) >> 8) |
((a & 0x0000FF0000000000ULL) >> 24) |
((a & 0x00FF000000000000ULL) >> 40) |
((a & 0xFF00000000000000ULL) >> 56);
return a;
}
Que diriez-vous d'une version générique, qui ne dépend pas de la taille d'entrée (certaines des implémentations ci-dessus supposent que unsigned long long
est de 64 bits, ce qui n'est pas nécessairement toujours vrai):
// converts an arbitrary large integer (preferrably >=64 bits) from big endian to Host machine endian
template<typename T> static inline T bigen2Host(const T& x)
{
static const int one = 1;
static const char sig = *(char*)&one;
if (sig == 0) return x; // for big endian machine just return the input
T ret;
int size = sizeof(T);
char* src = (char*)&x + sizeof(T) - 1;
char* dst = (char*)&ret;
while (size-- > 0) *dst++ = *src--;
return ret;
}
Que diriez-vous:
#define ntohll(x) ( ( (uint64_t)(ntohl( (uint32_t)((x << 32) >> 32) )) << 32) |
ntohl( ((uint32_t)(x >> 32)) ) )
#define htonll(x) ntohll(x)
J'aime la réponse du syndicat, plutôt soignée. En règle générale, je change légèrement de bit pour convertir entre le petit et le gros endian, bien que je pense que la solution d'union a moins d'affectations et peut être plus rapide:
//note UINT64_C_LITERAL is a macro that appends the correct prefix
//for the literal on that platform
inline void endianFlip(unsigned long long& Value)
{
Value=
((Value & UINT64_C_LITERAL(0x00000000000000FF)) << 56) |
((Value & UINT64_C_LITERAL(0x000000000000FF00)) << 40) |
((Value & UINT64_C_LITERAL(0x0000000000FF0000)) << 24) |
((Value & UINT64_C_LITERAL(0x00000000FF000000)) << 8) |
((Value & UINT64_C_LITERAL(0x000000FF00000000)) >> 8) |
((Value & UINT64_C_LITERAL(0x0000FF0000000000)) >> 24) |
((Value & UINT64_C_LITERAL(0x00FF000000000000)) >> 40) |
((Value & UINT64_C_LITERAL(0xFF00000000000000)) >> 56);
}
Ensuite, pour détecter si vous avez même besoin de faire votre flip sans magie macro, vous pouvez faire une chose similaire à Pax, où quand un court-circuit est attribué à 0x0001, il sera 0x0100 sur le système endian opposé.
Alors:
unsigned long long numberToSystemEndian
(
unsigned long long In,
unsigned short SourceEndian
)
{
if (SourceEndian != 1)
{
//from an opposite endian system
endianFlip(In);
}
return In;
}
Donc, pour utiliser cela, vous auriez besoin que SourceEndian soit un indicateur pour communiquer l'endianité du numéro d'entrée. Cela peut être stocké dans le fichier (s'il s'agit d'un problème de sérialisation), ou communiqué sur le réseau (s'il s'agit d'un problème de sérialisation réseau).
Un moyen simple serait d'utiliser ntohl sur les deux parties séparément:
unsigned long long htonll(unsigned long long v) {
union { unsigned long lv[2]; unsigned long long llv; } u;
u.lv[0] = htonl(v >> 32);
u.lv[1] = htonl(v & 0xFFFFFFFFULL);
return u.llv;
}
unsigned long long ntohll(unsigned long long v) {
union { unsigned long lv[2]; unsigned long long llv; } u;
u.llv = v;
return ((unsigned long long)ntohl(u.lv[0]) << 32) | (unsigned long long)ntohl(u.lv[1]);
}
htonl
peut être fait par les étapes ci-dessous
De même pour ntohll
également
#define HTONLL(x) ((1==htonl(1)) ? (x) : (((uint64_t)htonl((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32)))
#define NTOHLL(x) ((1==ntohl(1)) ? (x) : (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32)))
Vous pouvez également supprimer les définitions ci-dessus en tant que fonctions.
template <typename T>
static T ntoh_any(T t)
{
static const unsigned char int_bytes[sizeof(int)] = {0xFF};
static const int msb_0xFF = 0xFF << (sizeof(int) - 1) * CHAR_BIT;
static bool Host_is_big_endian = (*(reinterpret_cast<const int *>(int_bytes)) & msb_0xFF ) != 0;
if (Host_is_big_endian) { return t; }
unsigned char * ptr = reinterpret_cast<unsigned char *>(&t);
std::reverse(ptr, ptr + sizeof(t) );
return t;
}
Fonctionne sur 2 octets, 4 octets, 8 octets et 16 octets (si vous avez un entier de 128 bits). Doit être indépendant du système d'exploitation/de la plate-forme.
fonction universelle pour toute taille de valeur.
template <typename T>
T swap_endian (T value)
{
union {
T src;
unsigned char dst[sizeof(T)];
} source, dest;
source.src = value;
for (size_t k = 0; k < sizeof(T); ++k)
dest.dst[k] = source.dst[sizeof(T) - k - 1];
return dest.src;
}
Cela suppose que vous codez sous Linux en utilisant un système d'exploitation 64 bits; la plupart des systèmes ont htole(x)
ou ntobe(x)
etc, ce sont généralement des macros des divers bswap
#include <endian.h>
#include <byteswap.h>
unsigned long long htonll(unsigned long long val)
{
if (__BYTE_ORDER == __BIG_ENDIAN) return (val);
else return __bswap_64(val);
}
unsigned long long ntohll(unsigned long long val)
{
if (__BYTE_ORDER == __BIG_ENDIAN) return (val);
else return __bswap_64(val);
}
Note latérale; ce ne sont que des fonctions à appeler pour échanger l'ordre des octets. Si vous utilisez par exemple un petit endian avec un grand réseau endian, mais si vous utilisez un codage à grande fin, cela inversera inutilement l'ordre des octets, donc un peu "if __BYTE_ORDER == __LITTLE_ENDIAN
"une vérification pourrait être nécessaire pour rendre votre code plus portable, en fonction de vos besoins.
Mise à jour: édité pour montrer un exemple de vérification endian