【发布时间】:2014-10-02 21:14:35
【问题描述】:
在 Plan 9 源代码中,我经常找到这样的代码,用于从具有明确定义的字节顺序的缓冲区中读取序列化数据:
#include <stdint.h>
uint32_t le32read(uint8_t buf[static 4]) {
return (buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24);
}
我希望 gcc 和 clang 都能将这段代码编译成像 amd64 上的这个程序集一样简单的东西:
.global le32read
.type le32read,@function
le32read:
mov (%rdi),%eax
ret
.size le32read,.-le32read
但与我的预期相反,gcc 和 clang 都没有认识到这种模式,而是产生了具有多个班次的复杂程序集。
对于这种操作,是否有一个习惯用法既可以移植到所有 C99 实现,又可以跨实现生成良好(即像上面介绍的那样)代码?
【问题讨论】:
-
您没有指定您尝试编译的优化级别。你试过 -O2 吗?
-
@JonathonReinhart 我使用 -O3 编译,如果出现 clang,我还检查了显示初始化未完成的 llvm 中间代码。
-
我假设您的意思是
uint8_t buf[4](没有static,这没有任何意义)。作为参数,数组被解释为指针,所以它就是uint8_t *buf。所以编译器必须通过指针访问内存,而不是通过变量。当涉及到访问内存时,当涉及到编译器可以做出的假设时,事情就变得更加棘手了,因为可以通过不同的方式(指针算法,编译器的外部方式)访问内存。即使这种特殊情况看起来微不足道,但作为一般情况,这是不可能的。 -
@bolov
uint8_t buf[static 4]是一种新语法,它断言编译器访问buf的最多四个元素是明确定义的。我专门讨论了 x86 上的程序集是什么样子的,因为 x86 允许各种未对齐的内存访问,这使得编译器优化变得更加容易——在大多数情况下,编译器不必对内存对齐做出任何假设。我看不出这种优化将如何成为未定义的行为(在 amd64 上)。 -
@bolov 我上面链接的代码使用 gcc 编译为 20 条指令。二十条指令以明确定义的字节顺序从内存中读取单个整数,为此有专门的指令(即
bswap),因此可以在一两条指令中完成。大多数时候,我正在操作的数据已经在 L3 缓存中,因为它是在几个周期前从其他地方(即网络、文件系统、解压缩器)获取的。即使内存主导了这些操作的运行时,它们也经常出现,足以要求编译器正确优化它们。
标签: c optimization endianness idioms