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

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.)有什么技巧可以按照我正在寻找的方式有效地移位字节吗?

提前感谢您的见解.

nne*_*neo 6

使用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)