web-dev-qa-db-fra.com

64 bits ntohl () en C ++?

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).

58
Tom

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.

53
Nanno Langstraat

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!

17
user442585

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
14
paxdiablo

certains systèmes BSD ont betoh64 qui fait ce dont vous avez besoin.

12
Francis

Réponse rapide

#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

En tête de fichier

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é.

Portabilité

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

C++ 14 constexpr fonction de modèle

Gé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;
}

C++ 11 constexpr fonction de modèle

  • C++ 11 n'autorise pas la variable locale dans la fonction constexpr.
    L'astuce consiste donc à utiliser un argument avec une valeur par défaut.
  • De plus, la fonction C++ 11 constexpr doit contenir une seule expression.
    Par conséquent, le corps est composé d'un retour contenant des instructions séparées par des virgules .
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 ).

C++ 11 constexpr modèle fonctions SFINAE

Cependant, 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
}

Code de test de la première version

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';

Code de test de la deuxième version

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';

Sortie

               B
               B
            1122
            2211
        11223344
        44332211
1122334455667788
8877665544332211

Génération de code

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

11
olibre

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))
6
Sandeep
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;
}
5
kuzne4ik

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;
    }
5
Andrei Bozantan

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)
3
Matt

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).

2
Snazzer

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]);
}
2
bdonlan

htonl peut être fait par les étapes ci-dessous

  • Si son grand système endian renvoie directement la valeur. Pas besoin de faire de conversion. Si son petit système endian, doit faire la conversion ci-dessous.
  • Prenez LSB 32 bits et appliquez "htonl" et décalez 32 fois.
  • Prenez MSB 32 bits (en décalant la valeur uint64_t 32 fois vers la droite) et appliquez 'htonl'
  • Maintenant, appliquez le bit OR pour la valeur reçue aux 2e et 3e étapes.

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.

1
rashok
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.

0
zhaorufei

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;
}
0
Виталий

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

0
jwbensley