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个回答

2

一个简单的方法是分别对两个部分使用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]);
}

1
你的第一个函数是htonll,并在内部使用ntohl()。这两个函数是可以互换的,对吗?如果是这样,为什么它们被实现得不同呢? - Marius
糟糕,问题已修复。严格来说,字节序除了大端和小端之外还有其他选项 - 虽然现在很少见,但在一些非常老旧的系统上,htonl()ntohl() 的行为可能会有所不同。 - bdonlan

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

你可以将上述两个定义声明为函数。

0

适用于任何数值大小的通用函数。

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

1
仅仅交换字节是不够的。你必须知道输入值是否已经在目标大小端存储模式下,只有在需要时才进行字节交换。hton...()ntoh...() 函数处理这种逻辑。 - Remy Lebeau

0
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位整数)。应该是操作系统/平台无关的。


0

假设您使用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"检查,以根据您的需求使代码更具可移植性。

更新:编辑以显示字节顺序检查示例


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

-1
通常情况下,将主机整数转换为网络顺序并不需要了解计算机的字节序。但不幸的是,这仅限于将您的网络顺序值写成字节形式,而不是另一个整数的形式:
static inline void short_to_network_order(uchar *output, uint16_t in)
{
    output[0] = in>>8&0xff;
    output[1] = in&0xff;
}

(根据需要扩展以处理更大的数字)。

这将在任何架构上运行,因为在内存中布置整数的方式我没有使用特殊的知识点,而且在大端架构中应该会被优化,因为现代编译器不是愚蠢的。

缺点是,当然,这不是与 htonl() 和 friends 相同的标准接口(我不认为这是缺点,因为 imo 的 htonl() 设计是一个糟糕的选择)。


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