以32/64位数量有效地对字节进行位移?

时间:2016-05-07 02:29:52

标签: c bit-manipulation bit-shift

为简单起见,假设我使用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;

虽然这有效,但我更倾向于使用处理器的原生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;

上面的代码段成功执行了一次转换,但不是我想要的方式。看来,由于我将缓冲区转换为unsigned int,因此加载了寄存器(little-endian),其值为0x46efcdab(而不是0xabcdef46)。因此,执行4位左移会产生0xb0dafc6e而不是0xbcdef460

除了在移位之前交换字节(例如htonl等),是否有任何以我正在寻找的方式有效地移位字节的技巧?

提前感谢您的见解。

2 个答案:

答案 0 :(得分:6)

使用result = Customer.objects.filter(**query) / htonl网络(big-endian)字节顺序和 native 字节顺序之间切换:

ntohl

实际上,这会将缓冲区内容作为big-endian顺序的整数加载,执行shift,然后以big-endian顺序将其写回。

这会在x86上编译成几条uint32_t *p = (uint32_t*)buffer; *p = htonl(ntohl(*p) << 4); 指令,因此它应该相当有效(bswap)。

这里有一些测试代码(gcc -O3是全局的,以避免常量折叠,而buffer可以防止死代码消除):

return

这将编译成以下相当简单的机器代码(x86-64; LLVM 7.0.2; #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; } ):

cc -O2

答案 1 :(得分:3)

仅用于比较,您可以在不使用htonl / ntohl的情况下执行此操作。假设一个小端CPU:

#include <stdint.h>

void lshift(unsigned char* buf) {
  uint32_t* p = (uint32_t*)buf;
  uint32_t lo = *p & 0x0F0F0F0F;
  uint32_t hi = *p & 0xF0F0F000;
  *p = (lo << 4) | (hi >> 12);
}

使用gcc -O3生成的程序集:

pushq   %rbp
movq    %rsp, %rbp
movl    (%rdi), %eax
movl    %eax, %ecx
shll    $4, %ecx
andl    $-252645136, %ecx       ## imm = 0xFFFFFFFFF0F0F0F0
shrl    $12, %eax
andl    $986895, %eax           ## imm = 0xF0F0F
orl     %ecx, %eax
movl    %eax, (%rdi)
popq    %rbp
retq

取决于bswapl的周期数,它可能是更快的替代方案。