dig*_*ale 4 c bit-manipulation bit-shift
为简单起见,假设我使用的是32位小端处理器并声明了以下4字节缓冲区:
unsigned char buffer[] = { 0xab, 0xcd, 0xef, 0x46 };
Run Code Online (Sandbox Code Playgroud)
假设我的目标是逐位左移将缓冲区中的每个字节移位4位.也就是说,我想将缓冲区值转换为:
{ 0xbc, 0xde, 0xf4, 0x60 }.要执行这样的转换,可以编写如下代码:
for (int i = 0; i < 3; ++i)
{
buffer[i] <<= 4;
buffer[i] |= (buffer[i + 1] >> 4);
}
buffer[3] <<= 4;
Run Code Online (Sandbox Code Playgroud)
虽然这有效,但我更倾向于使用处理器的本机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;
Run Code Online (Sandbox Code Playgroud)
上面的片段成功地执行了一次转换,但不是我正在寻找的方式.看来,因为我将缓冲区转换为unsigned int,所以寄存器被加载(little-endian)并带有值0x46efcdab(而不是0xabcdef46).因此,执行4位左移0xb0dafc6e而不是0xbcdef460.
除了在移位之前交换字节(例如htonlet al.)有什么技巧可以按照我正在寻找的方式有效地移位字节吗?
提前感谢您的见解.
使用htonl/ ntohl在网络(大端)字节顺序和本机字节顺序之间切换:
uint32_t *p = (uint32_t*)buffer;
*p = htonl(ntohl(*p) << 4);
Run Code Online (Sandbox Code Playgroud)
实际上,这会将缓冲区内容作为big-endian顺序的整数加载,执行shift,然后以big-endian顺序将其写回.
这将编译成bswapx86上的几条指令,因此它应该是合理有效的(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;
}
Run Code Online (Sandbox Code Playgroud)
这将编译成以下相当简单的机器代码(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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
169 次 |
| 最近记录: |