【问题标题】:Is there any "standard" htonl-like function for 64 bits integers in C++?C++ 中是否有任何用于 64 位整数的“标准”类似 htonl 的函数?
【发布时间】:2011-03-02 15:23:43
【问题描述】:

我正在研究 memcache 协议的实现,该协议在某些时候使用 64 位整数值。这些值必须以“网络字节顺序”存储。

我希望有一些 uint64_t htonll(uint64_t value) 函数来进行更改,但不幸的是,如果它存在,我找不到它。

所以我有 1 或 2 个问题:

  • 是否有任何便携(Windows、Linux、AIX)标准功能可以做到这一点?
  • 如果没有这个功能,你会如何实现?

我想到了一个基本的实现,但我不知道如何在编译时检查字节序以使代码可移植。因此,这里非常欢迎您的帮助;)

谢谢。


这是我写的最终解决方案,感谢 Brian 的解决方案。

uint64_t htonll(uint64_t value)
{
    // The answer is 42
    static const int num = 42;

    // Check the endianness
    if (*reinterpret_cast<const char*>(&num) == num)
    {
        const uint32_t high_part = htonl(static_cast<uint32_t>(value >> 32));
        const uint32_t low_part = htonl(static_cast<uint32_t>(value & 0xFFFFFFFFLL));

        return (static_cast<uint64_t>(low_part) << 32) | high_part;
    } else
    {
        return value;
    }
}

【问题讨论】:

  • @ereOn:我也有类似的问题here。如果可能的话,你能看看,让我知道我在这里做错了什么吗?
  • 不要将您的答案包含在问题中,而应该让您的答案带有答案。它更具可读性。

标签: c++ 64-bit portability endianness htonl


【解决方案1】:
#define htonll(x) ((1==htonl(1)) ? (x) : ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
#define ntohll(x) ((1==ntohl(1)) ? (x) : ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))

测试 (1==htonl(1)) 只是确定(可悲的是在运行时)硬件架构是否需要字节交换。没有任何可移植的方法可以在编译时确定架构是什么,因此我们求助于使用“htonl”,它在这种情况下具有可移植性。如果需要字节交换,那么我们使用 htonl 一次交换 32 位(记住也要交换两个 32 位字)。


这是执行交换的另一种方法,可在大多数编译器和操作系统(包括 AIX、BSD、Linux 和 Solaris)之间移植。

#if __BIG_ENDIAN__
# define htonll(x) (x)
# define ntohll(x) (x)
#else
# define htonll(x) ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
# define ntohll(x) ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
#endif

重要的部分是使用__BIG_ENDIAN__或__LITTLE_ENDIAN__;而不是__BYTE_ORDER__、__ORDER_BIG_ENDIAN__ 或__ORDER_LITTLE_ENDIAN__。有些编译器和操作系统缺少__BYTE_ORDER__和朋友。

【讨论】:

  • 我确信这个答案很好,但您能否添加除代码之外的其他内容来解释什么以及为什么?
  • 谢谢!我已经用解释编辑了你的答案。
  • 如@ereon 所述,您不能在ntohl 或ntohl 上使用32 位移位,因为它们返回uint32_t,因此会触发UB。
【解决方案2】:

好吧,我认为在编译时尽可能使用字节序切换会更好,但我更喜欢使用函数而不是宏,因为在宏中,参数只是被参数替换;因此,如果参数在宏中多次出现(如之前提供的一些解决方案中所做的那样),参数可以被多次评估并产生奇怪的结果。

uint64_t htonll(uint64_t x)
{
#if __BIG_ENDIAN__
    return x;
#else
    return ((uint64_t)htonl((x) & 0xFFFFFFFFLL) << 32) | htonl((x) >> 32);
#endif
}

uint64_t ntohll(uint64_t x)
{
#if __BIG_ENDIAN__
    return x;
#else
    return ((uint64_t)ntohl((x) & 0xFFFFFFFFLL) << 32) | ntohl((x) >> 32);
#endif
}

所以这允许调用 htonll(x++) 而不增加 x 几次,就像使用前面的宏一样。

【讨论】:

    【解决方案3】:

    你可以试试uint64_t htobe64(uint64_t host_64bits) & uint64_t be64toh(uint64_t big_endian_64bits) 反之亦然。

    【讨论】:

    • 好吧,不,我不能,因为我已经 6 年多没接触那个代码库了;)
    【解决方案4】:

    减少“if num == ...”的开销 使用预处理器定义:

    #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
    #else
    #endif
    

    【讨论】:

    • 不可移植。 __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 是 Linux 的东西。 AIX 缺少它,并将进入大端代码路径。
    【解决方案5】:

    这似乎在 C 中有效;我做错什么了吗?

    uint64_t htonll(uint64_t value) {
        int num = 42;
        if (*(char *)&num == 42) {
            uint32_t high_part = htonl((uint32_t)(value >> 32));
            uint32_t low_part = htonl((uint32_t)(value & 0xFFFFFFFFLL));
            return (((uint64_t)low_part) << 32) | high_part;
        } else {
            return value;
        }
    }
    

    【讨论】:

      【解决方案6】:

      编辑:结合两者(使用布莱恩的代码):

      uint64_t htonll(uint64_t value)
      {
           int num = 42;
           if(*(char *)&num == 42)
                return (htonl(value & 0xFFFFFFFF) << 32LL) | htonl(value >> 32);
           else 
                return value;
      }
      

      警告:未经测试的代码!使用前请测试。

      【讨论】:

      • @Pavel 仍然不起作用。 htonl() 返回一个 32 位值,您不能调用 &lt;&lt; 32LL(因为您不能在只有 32 位的值上左移 32 位)。
      • 我认为移动 32LL 会促进左侧,不是吗?
      • @Pavel:不,移位值的位大小不会改变任何东西。 gcc 发出警告:“左移计数 >= 类型宽度”。你的函数说 htonll(0x0102030405060708ULL) == 0xc070605.
      • 把它改成return ((uint64_t)htonl(value &amp; 0xFFFFFFFF) &lt;&lt; 32LL) | htonl(value &gt;&gt; 32);就可以了。
      【解决方案7】:

      您可能正在寻找 bswap_64 我认为它几乎在所有地方都受支持,但我不会将其称为标准。

      您可以通过创建值为 1 的 int、将 int 的地址转换为 char* 并检查第一个字节的值来轻松检查字节顺序。

      例如:

      int num = 42;
      if(*(char *)&num == 42)
      {
         //Little Endian
      }
      else
      {
         //Big Endian
      } 
      

      了解这一点后,您还可以创建一个简单的函数来进行交换。


      您也可以始终使用包含可移植跨平台的字节序宏的 boost。

      【讨论】:

      • 谢谢 ;) boost 宏看起来很有趣。有链接吗?
      • 从技术上讲,仅仅一个字节交换并不总是足够的。有些系统是中端的(尽管没有现代系统)。此外,bswap_64 绝对不在所有地方都受支持;只需查看缺少bswap_64 的一长串系统:gnu.org/software/gnulib/manual/html_node/bswap_005f64.html
      • 请注意bswap_64 不是POSIX compliant。
      • bswap_64 在 Solaris i86pc 上不存在。有谁知道它是否存在于 AIX 上?
      • 为了确保其他人知道,bswap_64 总是假设需要交换,而htobe64 和htonl 一样,如果网络字节顺序(大端序)匹配计算机系统的字节顺序/字节序。
      猜你喜欢
      • 2014-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-27
      • 2012-03-25
      • 2010-09-14
      相关资源
      最近更新 更多