【问题标题】:Optimal and portable conversion of endian in c/c++c/c++ 中字节序的最佳和可移植转换
【发布时间】:2016-04-15 22:34:59
【问题描述】:

给定一个需要解析的具有 32 位 little-endian 字段的二进制文件,我想编写能够正确编译的解析代码,而与执行该代码的机器的字节序无关。目前我使用

uint32_t fromLittleEndian(const char* data){
  return uint32_t(data[3]) << (CHAR_BIT*3) |
         uint32_t(data[2]) << (CHAR_BIT*2) |
         uint32_t(data[1]) << CHAR_BIT |
         data[0]; 
}

然而,这会产生不理想的装配。在我的机器上g++ -O3 -S 产生:

_Z16fromLittleEndianPKc:
.LFB4:
    .cfi_startproc
    movsbl  3(%rdi), %eax
    sall    $24, %eax
    movl    %eax, %edx
    movsbl  2(%rdi), %eax
    sall    $16, %eax
    orl %edx, %eax
    movsbl  (%rdi), %edx
    orl %edx, %eax
    movsbl  1(%rdi), %edx
    sall    $8, %edx
    orl %edx, %eax
    ret
    .cfi_endproc

为什么会这样? 在小端机器上编译时,我如何说服它生成最佳代码

_Z17fromLittleEndian2PKc:
.LFB5:
    .cfi_startproc
    movl    (%rdi), %eax
    ret
    .cfi_endproc

我通过编译得到的:

uint32_t fromLittleEndian2(const char* data){
    return *reinterpret_cast<const uint32_t*>(data);
}

由于我知道我的机器是 little-endian,我知道上面的汇编是最佳的,但是如果在 big-endian 机器上编译它会失败。它还违反了严格的别名规则,所以如果内联它甚至可能在小端机器上产生 UB。 如果可能,是否有一个有效的代码将被编译为最佳程序集?

由于我希望我的函数会被大量内联,因此任何类型的运行时字节序检测都是不可能的。编写最佳 C/C++ 代码的唯一替代方法是使用编译时字节序检测,如果目标字节序不是小字节序,则使用templates 或#defines 退回到低效代码。然而,这似乎很难以便携方式完成。

【问题讨论】:

  • 您无法匹配reinterpret_cast。它没有进行任何字节重新排序。如果你必须跳字节序,你必须付钱给乐队。
  • 鉴于您正在解析文件,与您从 HDD 读取数据所花费的实际时间相比,您不会调用像 htonl() 这样的东西?
  • AFAICT 您无法通过模板知道 - 找出字节顺序的唯一方法本质上是通过不同类型的指针重新解释数据,而这在模板中是不允许的。就个人而言,我只会使用您愿意支持的编译器提供的一些#define(可能还有一些用于交换字节的编译器); gcc 提供了__BYTE_ORDER____bswap_32,其他编译器也会有类似的东西。更好的是,您可以只使用 boost.Endian 并将处理各种编译器的问题委托给它们。
  • 我同意。一个足够聪明的编译器应该能够生成一棵可爱的树,将它洗牌到最小,几乎就是你那里的东西,然后遍历逻辑,看看没有发生任何事情,基本上把整个事情都扔掉了的副本。但看起来我们还没有到达那里。
  • 顺便说一下,关于“可能比hton 更快”:至少在Linux 上的gcc 上,I wouldn't bet on it;为htonl 生成的代码可能是最佳的,具有幼稚转变的代码 - 我不会这么说。

标签: c++ optimization endianness


【解决方案1】:

简短的回答 - 使用 htonl - 它会被优化到 wazzoo

【讨论】:

  • 唯一的问题是网络顺序是大端的。
  • yup 和 htonl 会知道这一点并根据其运行的机器进行转换
  • 我知道,但是htonl 和朋友总是在机器端和大端(网络端)之间进行转换。根据定义,我的文件是 little-endian,我需要一个从/到机器端到/从小端转换的功能集。我看不出我可以使用htonlntohl 来解决我的问题,除了可能总是转换为大端,然后总是做一些字节洗牌。这不太可能接近最佳状态。
【解决方案2】:

我所知道的各种平台库通过#define 宏为字节序交换例程执行此操作,基于#define BIG_ENDIAN 的值。在源字节序与目标字节序匹配的情况下,您可以:

#ifdef LITTLE_ENDIAN
    #define fromLittleEndian(x) (x)
#else
    #define fromLittleEndian(x) _actuallySwapLittle((x))
#endif

例如:

http://man7.org/linux/man-pages/man3/endian.3.html

http://fxr.watson.org/fxr/source/sys/endian.h

【讨论】:

  • &lt;endian.h&gt; 似乎不可移植。见gcc.gnu.org/ml/gcc-help/2007-07/msg00342.html
  • 您必须在此处选择 - 最佳或便携。 @j_kubik 有一个非最佳的可移植版本。各种其他答案将建议其他或多或少可移植或最佳的技术,但确保您获得在不做任何事情的情况下不执行任何操作的唯一方法是使用预处理器。不能保证任何给定的 C++ 编译器都能识别出不做任何事情的情况。
  • 我想答案是尝试检测目标平台,如果不成功,则使用次优代码作为后备。
  • 最新版本的 BOOST 有一个字节序库,我相信它是高度可移植的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
  • 1970-01-01
  • 2011-11-06
相关资源
最近更新 更多