为简单起见,假设我使用的是 32 位小端处理器并声明了以下 4 字节缓冲区:
unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 };
假设我的目标是将缓冲区中的每个字节按位左移 4 位。也就是说,我想将缓冲区值转换为:
{ 0xbc、0xde、0xf4、0x60 }
。要执行这样的转换,可以编写如下代码:
for (int i = 0; i < 3; ++i)
{
buffer[i] <<= 4;
buffer[i] |= (buffer[i + 1] >> 4);
}
buffer[3] <<= 4;
虽然这可行,但我更愿意使用处理器的 native 32 位寄存器同时移动所有 4 个字节:
unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 };
unsigned int *p = (unsigned int*)buffer; // unsigned int is 32 bit on my platform
*p <<= 4;
上面的代码片段成功地执行了一次转换,但不是我正在寻找的方式。看来,由于我将缓冲区转换为无符号整数,寄存器加载(小端)值 0x46efcdab
(而不是 0xabcdef46
)。因此,执行 4 位左移会导致 0xb0dafc6e
而不是 0xbcdef460
。
除了在移位前交换字节(例如 htonl
等)之外,还有什么技巧可以按照我想要的方式有效地移位字节吗?
预先感谢您的见解。
最佳答案
使用htonl
/ntohl
在network(big-endian)字节顺序和native 字节顺序之间切换:
uint32_t *p = (uint32_t*)buffer;
*p = htonl(ntohl(*p) << 4);
实际上,这会将缓冲区内容作为整数以大端顺序加载,执行移位,然后以大端顺序将其写回。
这会在 x86 上编译成几个 bswap
指令,因此它应该相当高效 (gcc -O3
)。
下面是一些测试代码(buffer
是全局的以避免常量折叠,return
防止死代码消除):
#include <stdint.h> // uint32_t
#include <arpa/inet.h> // ntohl, htonl
unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 };
int main() {
uint32_t *p = (uint32_t*)buffer; // unsigned int is 32 bit on my platform
*p = htonl(ntohl(*p) << 4);
return *p;
}
这会编译成以下相当简单的机器代码(x86-64;LLVM 7.0.2;cc -O2
):
0000000000000000 pushq %rbp ; frame setup
0000000000000001 movq %rsp, %rbp ; frame setup
0000000000000004 movl (%rip), %eax ; load buffer
000000000000000a bswapl %eax ; endian flip
000000000000000c shll $0x4, %eax ; shift
000000000000000f bswapl %eax ; endian flip
0000000000000011 movl %eax, (%rip) ; save buffer
0000000000000017 popq %rbp ; finish
0000000000000018 retq
关于c - 以 32/64 位数量有效地移位字节?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37083879/