c - 如何让 GCC 在没有内置函数的情况下为大端存储生成 bswap 指令?

标签 c gcc x86 compiler-optimization endianness

更新:这已在 GCC 8.1 中修复。

我正在开发一个以大端格式将 64 位值存储到内存中的函数。我希望我可以编写适用于小端和大端平台的可移植 C99 代码,并让现代 x86 编译器自动生成 bswap 指令,无需任何内置或内在的。所以我从以下功能开始:

#include <stdint.h>

void
encode_bigend_u64(uint64_t value, void *vdest) {
    uint8_t *bytes = (uint8_t *)vdest;
    bytes[0] = value >> 56;
    bytes[1] = value >> 48;
    bytes[2] = value >> 40;
    bytes[3] = value >> 32;
    bytes[4] = value >> 24;
    bytes[5] = value >> 16;
    bytes[6] = value >> 8;
    bytes[7] = value;
}

这适用于将此函数编译为的 clang:

bswapq  %rdi
movq    %rdi, (%rsi)
retq

但是 GCC fails to detect the byte swap .我尝试了几种不同的方法,但它们只会让事情变得更糟。我知道 GCC 可以使用按位与、移位和按位或来检测字节交换,但为什么它在写入字节时不起作用?

编辑:我找到了对应的GCC bug .

最佳答案

这似乎可以解决问题:

void encode_bigend_u64(uint64_t value, void* dest)
{
  value =
      ((value & 0xFF00000000000000u) >> 56u) |
      ((value & 0x00FF000000000000u) >> 40u) |
      ((value & 0x0000FF0000000000u) >> 24u) |
      ((value & 0x000000FF00000000u) >>  8u) |
      ((value & 0x00000000FF000000u) <<  8u) |      
      ((value & 0x0000000000FF0000u) << 24u) |
      ((value & 0x000000000000FF00u) << 40u) |
      ((value & 0x00000000000000FFu) << 56u);
  memcpy(dest, &value, sizeof(uint64_t));
}

clang with -O3

encode_bigend_u64(unsigned long, void*):
        bswapq  %rdi
        movq    %rdi, (%rsi)
        retq

clang with -O3 -march=native

encode_bigend_u64(unsigned long, void*):
        movbeq  %rdi, (%rsi)
        retq

gcc 与 -O3

encode_bigend_u64(unsigned long, void*):
        bswap   %rdi
        movq    %rdi, (%rsi)
        ret

gcc with -O3 -march=native

encode_bigend_u64(unsigned long, void*):
        movbe   %rdi, (%rsi)
        ret

http://gcc.godbolt.org/ 上使用 clang 3.8.0 和 gcc 5.3.0 进行测试(所以我不知道到底是什么处理器(对于 -march=native)但我强烈怀疑最近的 x86_64 处理器)


如果您也想要一个适用于大端架构的函数,您可以使用 here 中的答案。检测系统的字节序并添加一个if。 union 和 pointer casts 版本都可以工作,并通过 gccclang 进行优化,从而产生完全相同的程序集(无分支)。 Full code on godebolt :

int is_big_endian(void)
{
    union {
        uint32_t i;
        char c[4];
    } bint = {0x01020304};

    return bint.c[0] == 1;
}

void encode_bigend_u64_union(uint64_t value, void* dest)
{
  if (!is_big_endian())
    //...
  memcpy(dest, &value, sizeof(uint64_t));
}

Intel® 64 and IA-32 Architectures Instruction Set Reference (3-542 卷 2A):

MOVBE—Move Data After Swapping Bytes

Performs a byte swap operation on the data copied from the second operand (source operand) and store the result in the first operand (destination operand). [...]

The MOVBE instruction is provided for swapping the bytes on a read from memory or on a write to memory; thus providing support for converting little-endian values to big-endian format and vice versa.

关于c - 如何让 GCC 在没有内置函数的情况下为大端存储生成 bswap 指令?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36497605/

相关文章:

c - 参数如何有类型但没有名称?

c - 从 C 函数返回数组

c - 专用加载和存储 ARM 指令会引发死锁吗?

c - 在 c 中第二次传递后,通过引用传递值不起作用

c - GCC如何检测堆栈缓冲区溢出

compiler-construction - 给定一个指令地址,能否确定包含它的函数的起始地址?

更改堆栈空间

gcc - 用clang编译时无法查看std::string

x86 - _mm_cmpistri 的模式 12

assembly - 为什么函数参数在 x86 上占用至少 4 个字节的堆栈?