簡體   English   中英

C++ 中的 64 位 ntohl()?

[英]64 bit ntohl() in C++?

htonl()的手冊頁似乎建議您最多只能將它用於 32 位值。 (實際上, ntohl()是為 unsigned long 定義的,在我的平台上是 32 位。我想如果 unsigned long 是 8 個字節,它可以用於 64 位整數)。

我的問題是我需要將 64 位整數(在我的例子中,這是一個 unsigned long long)從 big endian 轉換為 little endian。 現在,我需要進行特定的轉換。 但是,如果目標平台是大端的,如果函數(如ntohl() )不會轉換我的 64 位值,那就更好了。 (我寧願避免添加我自己的預處理器魔法來做到這一點)。

我可以使用什么? 我想要一些標准的東西,如果它存在的話,但我願意接受實施建議。 我曾經看到過使用聯合完成的這種類型的轉換。 我想我可以有一個帶有 unsigned long long 和 char[8] 的聯合。 然后相應地交換字節。 (顯然會在大端平台上中斷)。

文檔:Linux (glibc >= 2.9) 或 FreeBSD 上的man htobe64

不幸的是,在 2009 年的一次嘗試中,OpenBSD、FreeBSD 和 glibc (Linux) 並沒有很順利地協同工作來為此創建一個(非內核 API)libc 標准。

目前,這個簡短的預處理器代碼:

#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

(在 Linux 和 OpenBSD 上測試)應該隱藏差異。 它為您提供了這 4 個平台上的 Linux/FreeBSD 風格的宏。

使用示例:

  #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 庫”式的方法。

我建議閱讀: 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;
}

將顯示以下輸出:

1
72057594037927936
1

如果您刪除高 4 個字節,您可以使用 ntohl() 對此進行測試。

你也可以把它變成一個很好的 C++ 模板化函數,它可以處理任何大小的整數:

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

現在您的 128 位也安全了!

要檢測您的字節順序,請使用以下聯合:

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

然后你可以檢查xc[]的內容來檢測每個字節的去向。

要進行轉換,我將使用該檢測代碼一次以查看平台使用的字節序,然后編寫自己的函數來進行交換。

您可以使其動態化,以便代碼可以在任何平台上運行(檢測一次,然后在您的轉換代碼中使用一個開關來選擇正確的轉換)但是,如果您只打算使用一個平台,我會這樣做在單獨的程序中檢測一次,然后編寫一個簡單的轉換例程,確保您記錄它僅在該平台上運行(或已測試)。

這是我為說明它而編寫的一些示例代碼。 雖然沒有經過全面測試,但它已經過測試,但應該足以讓您入門。

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

請記住,這只是檢查純大/小端。 如果您有一些奇怪的變體,其中字節以 {5,2,3,1,0,7,6,4} 順序存儲,則cvt()會稍微復雜一些。 這樣的架構不應該存在,但我並沒有貶低我們微處理器行業朋友的瘋狂:-)

還要記住,這在技術上是未定義的行為,因為除了最后一個寫入的字段之外,您不應該通過任何字段訪問聯合成員。 它可能適用於大多數實現,但是,就純粹主義者的觀點而言,您可能應該硬着頭皮使用宏來定義自己的例程,例如:

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

快速回答

#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

頭文件

正如zhaorufei所報告的(見她/他的評論) endian.h不是 C++ 標准頭文件,宏__BYTE_ORDER__LITTLE_ENDIAN可能未定義。 因此#if語句是不可預測的,因為未定義的宏被視為0

如果您想分享您的 C++ 優雅技巧來檢測字節順序,請編輯此答案。

可移植性

此外,宏bswap_64()可用於 GCC 和 Clang 編譯器,但不適用於 Visual C++ 編譯器。 為了提供可移植的源代碼,您可能會受到以下代碼段的啟發:

#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

另請參閱更便攜的源代碼: Cross-platform _byteswap_uint64

C++14 constexpr模板函數

用於 16 位、32 位、64 位等的通用hton() ...

#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模板函數

  • C++11 不允許在constexpr函數中使用局部變量。
    因此,訣竅是使用具有默認值的參數。
  • 此外,C++11 constexpr函數必須包含一個單獨的表達式。
    因此,主體由具有一些逗號分隔語句的一個返回組成。
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;
}

使用-Wall -Wextra -pedantic在 clang-3.5 和 GCC-4.9 上沒有編譯警告
(參見在coliru上編譯和運行輸出)。

C++11 constexpr模板 SFINAE 函數

但是,上述版本不允許將constexpr變量創建為:

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

最后,我們需要根據 16/32/64 位來分離(專門化)函數。
但是我們仍然可以保留泛型函數。
(請參閱有關coliru的完整片段)

下面的 C++11 片段使用特征std::enable_if來利用替換失敗不是錯誤(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);
}

或者基於內置編譯器宏和 C++14 語法std::enable_if_t<xxx>的更短版本作為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

代碼生成

在線 C++ 編譯器gcc.godbolt.org顯示了生成的代碼。

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

注意:我的原始答案不符合 C++11- constexpr

這個答案在Public Domain CC0 1.0 Universal

一些 BSD 系統有betoh64您的需求。

在小端機器上用於 64 位交換的一行宏。

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

一個不依賴於輸入大小的通用版本怎么樣(上面的一些實現假設unsigned long long是 64 位,這不一定總是正確的):

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

怎么樣:

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

我喜歡工會的回答,非常簡潔。 通常我只是在小端和大端之間轉換,盡管我認為聯合解決方案的分配更少並且可能更快:

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

然后要檢測您是否甚至需要在沒有宏魔法的情況下進行翻轉,您可以執行與 Pax 類似的操作,其中當將短路分配給 0x0001 時,在相反的字節序系統上它將是 0x0100。

所以:

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

所以要使用它,你需要 SourceEndian 作為一個指示器來傳達輸入數字的字節序。 這可以存儲在文件中(如果這是序列化問題),或者通過網絡進行通信(如果是網絡序列化問題)。

一個簡單的方法是在兩個部分分別使用 ntohl:

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可以通過以下步驟完成

  • 如果它的大端系統直接返回值。 無需做任何轉換。 如果是小端系統,則需要做如下轉換。
  • 取 LSB 32 位並應用 'htonl' 並移位 32 次。
  • 取 MSB 32 位(通過將 uint64_t 值右移 32 次)並應用 'htonl'
  • 現在對第 2 步和第 3 步中收到的值應用按位 OR。

同樣對於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)))

您也可以將上述 2 定義定義為函數。

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

適用於 2 字節、4 字節、8 字節和 16 字節(如果您有 128 位整數)。 應該獨立於操作系統/平台。

這是假設您使用 64 位操作系統在 Linux 上編碼; 大多數系統都有htole(x)ntobe(x)等,這些通常是各種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);
}

邊注; 這些只是調用以交換字節順序的函數。 例如,如果您在大端網絡中使用小端,但如果您使用大端編碼,那么這將不必要地反轉字節順序,因此可能需要進行一點“ if __BYTE_ORDER == __LITTLE_ENDIAN ”檢查以使您的代碼更具可移植性,取決於您的需求。

更新:編輯以顯示字節序檢查的示例

任何值大小的通用函數。

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;
}
union help64
{
    unsigned char byte[8];
    uint64_t quad;
};
uint64_t ntoh64(uint64_t src)
{
    help64 tmp;
    tmp.quad = src;
    uint64_t dst = 0;
    for(int i = 0; i < 8; ++i)
        dst = (dst << 8) + tmp.byte[i];
    return dst;
}

通常不需要知道機器的字節序來將主機整數轉換為網絡順序。 不幸的是,只有當您以字節為單位寫出您的網絡順序值而不是另一個整數時,這才成立:

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

(根據需要擴展更大的數字)。

這將 (a) 適用於任何架構,因為我在任何時候都不會使用有關整數在內存中布局方式的特殊知識,並且 (b) 應該主要在大端架構中進行優化,因為現代編譯器並不愚蠢。

當然,缺點是這與 htonl() 和朋友的標准接口不同(我認為這不是缺點,因為 htonl() 的設計是一個糟糕的選擇)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM