为简单起见,假设我使用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
等),是否有任何以我正在寻找的方式有效地移位字节的技巧?
提前感谢您的见解。
答案 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
的周期数,它可能是更快的替代方案。