C++中的64位ntohl()是什么?

69

htonl()的手册似乎表明您只能使用它来处理最多32位的值。(实际上,ntohl()被定义为无符号长整型,在我的平台上是32位。我想如果无符号长整型是8字节,它将适用于64位整数)。

我的问题是我需要将64位整数(在我的情况下,这是一个无符号长长整型)从大端转换为小端。现在,我需要做这个特定的转换。但如果函数(例如ntohl())不会在目标平台为大端时转换我的64位值,则更好。(我宁愿避免添加自己的预处理器魔术来完成此操作)。

我可以使用什么?如果存在标准解决方案就更好了,但我也可以接受实现建议。我曾经看到过使用联合进行此类型转换的方法。我想我可以有一个带有无符号长长整型和char[8]的联合体。然后根据需要交换字节。(显然,它将在大端平台上出现错误)。


2
你使用的平台是什么?大多数系统都有特定于平台的大端到小端转换例程。如果没有,你可以轻松地编写一个。 - Jason Coco
请看我在这个问题的回答 - winden
2
仅代表个人意见,C标准(不知道是89还是99)清楚地写明long应该足以存储指针。然而,这个短语在C++标准中并没有出现。我见过的Linux编译器都遵守这一规定,在64位构建中,long为64位。然而,微软选择了一个奇怪的解决方案,即在任何地方long都是32位。 - v.oddou
@JasonCoco,你早在这个问题被提出时就抓住了要点。我认为我在下面发布的示例答案就是你想要的。 - jwbensley
对于那些感兴趣的人,这里给出了问题的实际答案 - Code Abominator
17个回答

64

文档: 在Linux (glibc >= 2.9)或FreeBSD上运行man htobe64

不幸的是,OpenBSD、FreeBSD和glibc(Linux)在2009年的一次尝试中并没有很好地协同工作来创建一个(非内核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上测试)应该隐藏差异。这将在这四个平台上提供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库”的方法。


10
这在安卓上无法运行(安卓定义了__linux__但提供了OpenBSD API)。 - Stefan
11
@Stefan: 那太可怕了 :( - Lightness Races in Orbit

21

我建议阅读这篇文章:http://commandcenter.blogspot.com/2012/04/byte-order-fallacy.html

这篇文章讨论了“字节顺序谬误”,对IT技术领域有着重要的意义。
#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位!

1
我认为你的模板版本有问题,它忽略了最后一个字节。为了修复它,我改变了size = sizeof(T);input >> ((size-i-1)*8) - Michael Anderson
这只是一种关于编译器抽象的猜测,针对那些比寄存器大小更大的类型,它们在内存中的存储方式如何。谁说他们想要严格遵守小端或大端?它甚至可以是一个不依赖于平台的设计,就像在一种架构上是自然的,在另一种架构上是不自然的。这并不重要,因为将大整数“加载”到寄存器中的代码是相同的,并且可移植的。但这个选择取决于编译器。 - v.oddou
1
访问内存作为uint8_t违反了严格别名规则,是未定义的行为。 - user3624760
有办法让它与结构体一起工作吗?对每个字节应用处理... 它在第一行(T的初始化)和缺少 operator>> 时产生错误。 - Sandburg

16

快速答案

#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

另外还有一个更加便携的源代码:跨平台 _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片段使用traits std::enable_if来利用Substitution Failure Is Not An Error(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 标准。
此答案属于 公共领域 CC0 1.0通用

htonT()中,你会给它一个char*输入参数而不是使用本地变量吗? - Remy Lebeau
感谢@RemyLebeau的反馈。C++11不允许在constexpr函数中使用局部变量 :-( 一年半过去了,C++14比仅仅使用C++11更加普遍。因此,我已经更新了答案,提供了在C++14中更清晰的constexpr函数。您是否确认我的更改?干杯 - oHo
endian.h 不是 C++ 标准头文件。当有 endian.h 文件时,如果没有定义 __BYTE_ORDER 和 __LITTLE_ENDIAN 宏,那么情况会更加危险。因为未定义的宏将被视为 0,从而它们相等。__bswap_constant_XX 是 gcc/clang 特有的。我想使用编译器宏来获得 GCC/Clang/MSVC 的最小可移植解决方案:#ifdef GNUC // also works for clang __builtin_bswap64/32/16 #elif defined(_MSC_VER) #else _byteswap_ushort/_byteswap_ulong/_byteswap_uint64 #error Not supported #endif - zhaorufei
非常感谢@zhaorufei :-) 由于您的反馈,我已经改进了答案 :-) 请查看答案并告诉我是否可以。我还没有编译片段... 请验证片段是否正确。谢谢。保重 - oHo

14

要检测数据的大小端,可以使用以下联合体:

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

然后您可以检查x.c[]的内容,以检测每个字节的位置。

要进行转换,我会使用检测代码一次来确定平台使用的端序,然后编写自己的函数来执行交换操作。

您可以使其动态化,以便代码在任何平台上运行(检测一次,然后在转换代码内部使用switch语句选择正确的转换方式),但如果您只想在一个平台上使用,我建议您仅在单独的程序中执行一次检测,然后编写简单的转换例程,确保您记录了它仅在该平台上运行(或已测试过)。

这里是一些我编写的示例代码,它已经经过测试,虽然不是非常全面,但应该足以让您开始工作。

#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

2
实际上,“return src”应该在大端架构中完成,而不是小端架构。此外,在小端CPU上执行转换的更简洁的方法是,通过对src的低32位使用htonl()计算结果的高32位,并通过对src的高32位使用htonl()计算结果的低32位(希望这有些意义...)。 - Lance Richardson
1
那不对,是吗,Lance?问题要求以小端方式表示值-这意味着在小端系统上保持原样,在大端系统上进行转换。 - paxdiablo
htonl()和相关函数旨在将主机CPU字节顺序转换为网络字节顺序。网络字节顺序是大端字节序,因此在小端字节序的计算机上需要进行转换。问题与此一致 - 它指出如果目标平台是大端字节序,则该函数不应转换我的64位值。 - Lance Richardson
要小心,不要做比实际需要更多的工作。不要混淆内存表示和寄存器(编译器抽象整数)表示:http://commandcenter.blogspot.fr/2012/04/byte-order-fallacy.html - v.oddou
1
为什么不使用 unsigned int a = 1; if(*((char *)&a) == 1) printf("little endian"); 呢? - KarimS
显示剩余8条评论

12

一些BSD系统具有betoh64函数,它可以满足您的需求。


4
Linux (glibc) 也使用这个函数。它在 <endian.h> 头文件中找到。 - ephemient
哦,我在任何endian.h头文件中都找不到这个函数。我现在正在我的英特尔Mac上(运行Leopard)。我还需要让它在学校的Linux机器上工作。我不确定正在运行哪个发行版,但我相当确定它们是i386机器,小端字节序,并且sizeof(unsigned long long) == 8。此外,我需要的函数是be64toh()。有什么建议吗?我更喜欢这个解决方案而不是其他解决方案。 - Tom
我的错 - 你想要的应该是betoh64。在FreeBSD上,它在/usr/include/sys/endian.h中。手册页是byteorder(9)。根据FreeBSD的注释,这些最初来自NetBSD,在5.x之后出现在FreeBSD上。据我所知,MacOSX使用许多FreeBSD文件作为其后端(darwin)基础 - 所以你很有可能能够使用它。 - Francis
@Francis:我的资料显示它甚至存在于4.3BSD中。 @Tom:Autoconf会查找endian.h、sys/endian.h和machinfo/endian.h;在不同的平台上,您可能需要使用不同的包含路径。 - ephemient

6

64位小端机器的一行宏,用于交换数据。

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

1
@BjornRoche 对于大端机器来说,构建类似的宏将会很容易。使用 #include <endian.h> #if __BYTE_ORDER == __LITTLE_ENDIAN 可以整理 bswap64() API 并使其与平台无关。 - Sandeep

5

那么如何实现一个通用版本,不依赖于输入大小(上面的一些实现假定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;
    }

1
目前最佳解决方案。我只需用for替换while,让编译器依赖于sizeof(T)展开循环即可。 - Yoav

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

3
为什么16位函数返回32位整数? - mafu

3
如何考虑:
#define ntohll(x) ( ( (uint64_t)(ntohl( (uint32_t)((x << 32) >> 32) )) << 32) | 
    ntohl( ((uint32_t)(x >> 32)) ) )                                        
#define htonll(x) ntohll(x)

2

我很喜欢使用union的解决方案,看起来非常不错。通常情况下,我会使用位移操作在小端和大端之间进行转换,尽管我认为union的解决方案更少赋值,并且可能更快:

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

那么为了检测你是否需要进行flip操作,无需使用宏魔法,可以像Pax一样做一个类似的事情,在将short赋值为0x0001时,它在相反的endian系统上将变为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作为指示器来传递输入数字的字节序。如果这是一个序列化问题,则可以将其存储在文件中;如果是网络序列化问题,则可以通过网络进行通信。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接