【发布时间】: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