64 bit ntohl () in C ++?

Le pagine man di htonl() sembrano suggerire che puoi usarlo solo per valori fino a 32 bit. (In realtà, ntohl() è definito per unsigned long, che sulla mia piattaforma è a 32 bit. Suppongo che se unsigned long fosse 8 byte, funzionerebbe per 64 bit int).

Il mio problema è che ho bisogno di convertire interi a 64 bit (nel mio caso, questo è un lungo non firmato) da big endian a little endian. In questo momento, ho bisogno di fare quella conversione specifica. Ma sarebbe ancora più bello se la funzione (come ntohl() ) non convertisse il mio valore a 64 bit se la piattaforma di destinazione fosse big endian. (Preferisco evitare di aggiungere la mia magia per il preprocessore per farlo).

Cosa posso usare? Vorrei qualcosa di standard se esiste, ma sono aperto a suggerimenti di implementazione. Ho visto questo tipo di conversione fatto in passato usando i sindacati. Suppongo che potrei avere un sindacato con un long lungo non firmato e un char [8]. Quindi scambiare i byte di conseguenza. (Ovviamente si romperebbe su piattaforms che erano big endian).

Documentazione: man htobe64 su Linux (glibc> = 2.9) o FreeBSD.

Sfortunatamente OpenBSD, FreeBSD e glibc (Linux) non hanno funzionato abbastanza bene insieme per creare uno standard libc (non-kernel-API) per questo, durante un tentativo nel 2009.

Attualmente, questo breve pezzo di codice del preprocessore:

 #if defined(__linux__) # include  #elif defined(__FreeBSD__) || defined(__NetBSD__) # include  #elif defined(__OpenBSD__) # include  # define be16toh(x) betoh16(x) # define be32toh(x) betoh32(x) # define be64toh(x) betoh64(x) #endif 

(testato su Linux e OpenBSD) dovrebbe hide le differenze. Ti dà le macro in stile Linux / FreeBSD su quelle 4 piattaforms.

Usa esempio:

  #include  // For 'uint64_t' uint64_t host_int = 123; uint64_t big_endian; big_endian = htobe64( host_int ); host_int = be64toh( big_endian ); 

È l’approccio più “standard della libreria C” disponibile al momento.

Consiglierei di leggere questo: http://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html

 #include  #include  #include  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; } 

Mostrerà il seguente output:

 1 72057594037927936 1 

È ansible testarlo con ntohl () se si rilasciano i 4 byte superiori.

Inoltre, puoi trasformarlo in una bella funzione basata su template in C ++ che funzionerà su qualsiasi numero intero di dimensioni:

 template  static inline T hton_any(const T &input) { T output(0); const std::size_t size = sizeof(input) - 1; uint8_t *data = reinterpret_cast(&output); for (std::size_t i = 0; i < size; i++) { data[i] = input >> ((size - i) * 8); } return output; } 

Ora anche i tuoi 128 bit sono sicuri!

Per rilevare la tua endianità, usa la seguente unione:

 union { unsigned long long ull; char c[8]; } x; x.ull = 0x0123456789abcdef; // may need special suffix for ULL. 

Quindi è ansible controllare il contenuto di xc[] per rilevare dove è andato ogni byte.

Per fare la conversione, vorrei usare quel codice di rilevamento una volta per vedere quale endianità la piattaforma sta usando, quindi scrivere la mia funzione per fare lo swap.

Puoi renderlo dinamico in modo che il codice venga eseguito su qualsiasi piattaforma (rilevi una volta quindi usa uno switch all’interno del tuo codice di conversione per scegliere la conversione corretta) ma, se stai per utilizzare una sola piattaforma, farei semplicemente il rilevamento una volta in un programma separato codifica una semplice procedura di conversione, assicurandosi di documentare che funziona solo (o è stato testato) su quella piattaforma.

Ecco un esempio di codice che ho montato per illustrarlo. È stato testato anche se non in modo approfondito, ma dovrebbe essere sufficiente per iniziare.

 #include  #include  #include  #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 = (xc[7] == 0x01) ? TYP_BIGE : TYP_SMLE; } if (typ == TYP_SMLE) return src; x.ull = src; c = xc[0]; xc[0] = xc[7]; xc[7] = c; c = xc[1]; xc[1] = xc[6]; xc[6] = c; c = xc[2]; xc[2] = xc[5]; xc[5] = c; c = xc[3]; xc[3] = xc[4]; xc[4] = c; return x.ull; } int main (void) { unsigned long long ull = 1; ull = cvt (ull); printf ("%llu\n",ull); return 0; } 

Tieni presente che questo controlla solo per puro big / little endian. Se si dispone di qualche strana variante in cui sono memorizzati i byte, ad esempio, {5,2,3,1,0,7,6,4} ordine, cvt() sarà un po ‘più complesso. Una tale architettura non merita di esistere, ma non sto scontando la follia dei nostri amici nel settore dei microprocessori 🙂

Inoltre, tieni presente che si tratta di un comportamento tecnicamente indefinito, in quanto non devi accedere a un membro del sindacato in base a un campo diverso da quello scritto in precedenza. Probabilmente funzionerà con la maggior parte delle implementazioni ma, per il punto di vista purista, dovresti probabilmente mordere il proiettile e usare le macro per definire le tue routine, ad esempio:

 // 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 

alcuni sistemi BSD hanno betoh64 che fa ciò di cui hai bisogno.

Risposta rapida

 #include  // __BYTE_ORDER __LITTLE_ENDIAN #include  // bswap_64() uint64_t value = 0x1122334455667788; #if __BYTE_ORDER == __LITTLE_ENDIAN value = bswap_64(value); // Compiler builtin GCC/Clang #endif 

File di intestazione

Come riportato da zhaorufei (vedi il suo commento) endian.h non è l’intestazione standard C ++ e le macro __BYTE_ORDER e __LITTLE_ENDIAN potrebbero essere indefinite. Pertanto l’istruzione #if non è prevedibile in quanto le macro non definite vengono considerate come 0 .

Si prega di modificare questa risposta se si desidera condividere il trucco elegante C ++ per rilevare endianness.

portabilità

Inoltre la macro bswap_64() è disponibile per i compilatori GCC e Clang ma non per il compilatore Visual C ++. Per fornire un codice sorgente portatile, potresti essere ispirato dal seguente frammento:

 #ifdef _MSC_VER #include  #define bswap_16(x) _byteswap_ushort(x) #define bswap_32(x) _byteswap_ulong(x) #define bswap_64(x) _byteswap_uint64(x) #else #include  // bswap_16 bswap_32 bswap_64 #endif 

Vedi anche un codice sorgente più portatile: cross-platform _byteswap_uint64

constexpr modello constexpr C ++ 14

hton() generico hton() per 16 bit, 32 bit, 64 bit e altro …

 #include  // __BYTE_ORDER __LITTLE_ENDIAN #include  // std::reverse() template  constexpr T htonT (T value) noexcept { #if __BYTE_ORDER == __LITTLE_ENDIAN char* ptr = reinterpret_cast(&value); std::reverse(ptr, ptr + sizeof(T)); #endif return value; } 

constexpr modello constexpr C ++ 11

  • C ++ 11 non consente la variabile locale nella funzione constexpr .
    Quindi il trucco è usare un argomento con valore predefinito.
  • Inoltre la funzione constexpr C ++ 11 deve contenere una sola espressione.
    Pertanto il corpo è composto da un ritorno con alcune istruzioni separate da virgola.
 template  constexpr T htonT (T value, char* ptr=0) noexcept { return #if __BYTE_ORDER == __LITTLE_ENDIAN ptr = reinterpret_cast(&value), std::reverse(ptr, ptr + sizeof(T)), #endif value; } 

Nessun avviso di compilazione su clang-3.5 e GCC-4.9 usando -Wall -Wextra -pedantic
(vedi compilation ed esegui output su coliru ).

constexpr modello constexpr C ++ 11

Tuttavia la versione precedente non consente di creare variabili constexpr come:

 constexpr int32_t hton_six = htonT( int32_t(6) ); 

Infine abbiamo bisogno di separare (specializzare) le funzioni a seconda dei 16/32/64 bit.
Ma possiamo ancora mantenere le funzioni generiche.
(guarda lo snippet completo su coliru )

Il seguente snippet C ++ 11 usa i tratti std::enable_if per sfruttare il fallimento di sostituzione non è un errore (SFINAE).

 template  constexpr typename std::enable_if::type htonT (T value) noexcept { return ((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8); } template  constexpr typename std::enable_if::type htonT (T value) noexcept { return ((value & 0x000000FF) << 24) | ((value & 0x0000FF00) << 8) | ((value & 0x00FF0000) >> 8) | ((value & 0xFF000000) >> 24); } template  constexpr typename std::enable_if::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); } 

O una versione ancora più breve basata sulle macro del compilatore e sulla syntax C ++ 14 std::enable_if_t come scorciatoia per std::enable_if::type :

 template  constexpr typename std::enable_if_t htonT (T value) noexcept { return bswap_16(value); // __bswap_constant_16 } template  constexpr typename std::enable_if_t htonT (T value) noexcept { return bswap_32(value); // __bswap_constant_32 } template  constexpr typename std::enable_if_t htonT (T value) noexcept { return bswap_64(value); // __bswap_constant_64 } 

Codice di prova della prima versione

 std::uint8_t uc = 'B'; std::cout < 

Codice di prova della seconda versione

 constexpr uint8_t a1 = 'B'; std::cout< 

Produzione

  B B 1122 2211 11223344 44332211 1122334455667788 8877665544332211 

Generazione del codice

Il compilatore online di C ++ gcc.godbolt.org indica il codice generato.

g++-4.9.2 -std=c++14 -O3

 std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT(unsigned char): movl %edi, %eax ret std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT(unsigned short): movl %edi, %eax rolw $8, %ax ret std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT(unsigned int): movl %edi, %eax bswap %eax ret std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT(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): # @std::enable_if<(sizeof (unsigned char))==(1), unsigned char>::type htonT(unsigned char) movl %edi, %eax retq std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT(unsigned short): # @std::enable_if<(sizeof (unsigned short))==(2), unsigned short>::type htonT(unsigned short) rolw $8, %di movzwl %di, %eax retq std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT(unsigned int): # @std::enable_if<(sizeof (unsigned int))==(4), unsigned int>::type htonT(unsigned int) bswapl %edi movl %edi, %eax retq std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT(unsigned long): # @std::enable_if<(sizeof (unsigned long))==(8), unsigned long>::type htonT(unsigned long) bswapq %rdi movq %rdi, %rax retq 

Nota: la mia risposta originale non era conforms a C ++ 11- constexpr .

Questa risposta è in Public Domain CC0 1.0 Universal

una macro di linea per lo scambio a 64 bit su macchine little endian.

 #define bswap64(y) (((uint64_t)ntohl(y)) << 32 | ntohl(y>>32)) 

Che ne dici di una versione generica, che non dipende dalla dimensione di input (alcune delle implementazioni precedenti presuppongono che unsigned long long sia a 64 bit, che non è necessariamente sempre vero):

  // converts an arbitrary large integer (preferrably >=64 bits) from big endian to host machine endian template 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; } 
 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; } 

Mi piace la risposta del sindacato, molto carina. In genere, mi sono appena mosso per convertire tra little e big endian, anche se penso che la soluzione sindacale abbia meno incarichi e possa essere più veloce:

 //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); } 

Quindi per scoprire se hai bisogno di fare il tuo flip senza macro magia, puoi fare una cosa simile a Pax, dove quando un corto è assegnato a 0x0001 sarà 0x0100 sul sistema endian opposto.

Così:

 unsigned long long numberToSystemEndian ( unsigned long long In, unsigned short SourceEndian ) { if (SourceEndian != 1) { //from an opposite endian system endianFlip(In); } return In; } 

Quindi, per utilizzarlo, è necessario che SourceEndian sia un indicatore per comunicare la endianità del numero di input. Questo potrebbe essere memorizzato nel file (se si tratta di un problema di serializzazione) o comunicato tramite la rete (se si tratta di un problema di serializzazione di rete).

Che ne dite di:

 #define ntohll(x) ( ( (uint64_t)(ntohl( (uint32_t)((x << 32) >> 32) )) << 32) | ntohl( ((uint32_t)(x >> 32)) ) ) #define htonll(x) ntohll(x) 

Un modo semplice sarebbe utilizzare ntohl sulle due parti separatamente:

 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]); } 
 template  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(int_bytes)) & msb_0xFF ) != 0; if (host_is_big_endian) { return t; } unsigned char * ptr = reinterpret_cast(&t); std::reverse(ptr, ptr + sizeof(t) ); return t; } 

Funziona per 2 byte, 4 byte, 8 byte e 16 byte (se si dispone di un numero intero a 128 bit). Dovrebbe essere OS / piattaforma indipendente.

Questo presuppone che tu stia codificando su Linux usando il SO a 64 bit; la maggior parte dei sistemi ha htole(x) o ntobe(x) ecc, questi sono in genere macro ai vari bswap

 #include  #include  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); } 

Nota a margine; queste sono solo funzioni da chiamare per scambiare l’ordinamento dei byte. Se si utilizza little endian ad esempio con una grande rete endian, ma se si utilizza la codifica if __BYTE_ORDER == __LITTLE_ENDIAN ciò annullerà inutilmente l’ordinamento dei byte, pertanto potrebbe essere necessario un piccolo controllo ” if __BYTE_ORDER == __LITTLE_ENDIAN ” per rendere il codice più portatile , depurato dalle tue esigenze.

Aggiornamento: modificato per mostrare un esempio di controllo endian

funzione universale per qualsiasi dimensione di valore.

 template  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; } 

htonl può essere fatto con i seguenti passaggi

  • Se il suo grande sistema endian restituisce direttamente il valore. Non c’è bisogno di fare alcuna conversione. Se il suo sistema di litte endian, ha bisogno di fare la conversione di seguito.
  • Prendi LSB a 32 bit e applica ‘htonl’ e sposta 32 volte.
  • Prendi MSB a 32 bit (spostando il valore uint64_t 32 volte a destra) e applica ‘htonl’
  • Ora applica il bit bit OR per il valore ricevuto in 2 ° e 3 ° passaggio.

Allo stesso modo anche per ntohll

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

Puoi anche considerare oltre 2 definizione come funzioni.

In generale non è necessario conoscere l’endianness di una macchina per convertire un numero intero host in un ordine di rete. Sfortunatamente ciò vale solo se si scrive il valore del proprio ordine netto in byte, piuttosto che come un altro intero:

 static inline void short_to_network_order(uchar *output, uint16_t in) { output[0] = in>>8&0xff; output[1] = in&0xff; } 

(estendere come richiesto per numeri più grandi).

Questo (a) funziona su qualsiasi architettura, perché in nessun caso utilizzo una conoscenza speciale sul modo in cui un intero è disposto in memoria e (b) dovrebbe principalmente ottimizzare nelle architetture big-endian perché i compilatori moderni non sono stupidi.

Lo svantaggio è, naturalmente, che questa non è la stessa interfaccia standard di htonl () e amici (che non vedo come uno svantaggio, perché il design di htonl () era una scelta sbagliata).