访问 builtin_bswap 函数的正确方法是什么?

时间:2021-04-03 21:12:09

标签: linux gcc endianness

我有一个使用数据库的应用程序,其中数据以大端顺序存储。为了跨硬件平台可移植地访问这些数据,我使用了 config.h 模块中定义的 4 个宏:

word(p) - gets a big-endian 16 bit value at pointer p as a native 16-bit value.
putword(p, w) - stores a native 16-bit variable (w) to pointer p as 16-bit big-endian.
dword(p) and putdword(p, d) do the same for 32-bit values

这一切都很好,但小端机器上的宏使用了蛮力的“移位和掩码”方法。

无论如何,看起来 linux 上有 builtin_bswap16 和 builtin_bswap32 函数可以更有效地执行此操作(作为内联汇编代码?)。那么对我的 word/putword 宏进行编码以便它们在 X86_64 linux 机器上使用这些内置函数的正确方法是什么?将我的宏编码为 htons/l 函数调用是否会有效地做同样的事情 - 是否有必要启用编译器优化以使这些解决方案中的任何一个工作?如果它使 gdb 无用,我宁愿不优化。

2 个答案:

答案 0 :(得分:0)

嗯。我编写了一个简单的测试程序,没有使用特殊的包含文件,只是直接调用 __builtin_swap... 函数(请参阅下面的“fast...”宏)。这一切都只是有效。当我在 gdb 中反汇编代码时,我看到 fast... 宏在 4-5 条汇编指令中完成,对于最坏情况的 'dword' 宏,最多需要 27 条指令。几乎不费吹灰之力就进行了相当巧妙的改进。

typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;

#define word(a)       (ushort) ( (*((uchar *)(a)) << 8) |          \
                                 (*((uchar *)(a) + 1)) )
#define putword(a,w)  *((char *)(a))   =  (char) (((ushort)((w) >>  8)) & 0x00ff), \
                      *((char *)(a)+1) =  (char) (((ushort)((w) >>  0)) & 0x00ff)
#define dword(a) (uint)  ( ((uint)(word(a)) << 16) |      \
                             ((uint)(word(((uchar *)(a) + 2)))) )
#define putdword(a,d) *((char *)(a))   =  (char) (((uint)((d) >> 24)) & 0x00ff), \
                      *((char *)(a)+1) =  (char) (((uint)((d) >> 16)) & 0x00ff), \
                      *((char *)(a)+2) =  (char) (((uint)((d) >>  8)) & 0x00ff), \
                      *((char *)(a)+3) =  (char) (((uint)((d) >>  0)) & 0x00ff)

#define fastword(a)   (ushort) __builtin_bswap16(* ((ushort *) a));
#define fastputword(a, w)  *((ushort *) a) =  __builtin_bswap16((ushort)w);
#define fastdword(a)   (uint) __builtin_bswap32(* ((uint *) a));
#define fastputdword(a, d)  *((uint *) a) =  __builtin_bswap32((uint)d);

int main()
{
unsigned short s1, s2, s3;
unsigned int i1, i2, i3;

        s1 = 0x1234;
        putword(&s2, s1);
        s3 = word(&s2);
        i1 = 0x12345678;
        putdword(&i2, i1);
        i3 = dword(&i2);
        printf("s1=%x, s2=%x, s3=%x, i1=%x, i2=%x, i3=%x\n", s1, s2, s3, i1, i2, i3);

        s1 = 0x1234;
        fastputword(&s2, s1);
        s3 = fastword(&s2);
        i1 = 0x12345678;
        fastputdword(&i2, i1);
        i3 = fastdword(&i2);
        printf("s1=%x, s2=%x, s3=%x, i1=%x, i2=%x, i3=%x\n", s1, s2, s3, i1, i2, i3);
}

答案 1 :(得分:0)

我只会使用 htons, htonl 和朋友。它们的可移植性要好得多,而且很可能任何给定 libc 的作者都将它们实现为内联函数或宏,这些函数或宏调用 __builtin 内在函数或内联 asm 或其他任何东西,从而导致几乎 -该特定机器的最佳实现。 See what is generated in godbolt's setup,我认为这是 Linux/glibc 的某种风格。

您确实需要使用优化来编译它们才能被内联,否则它会生成一个普通的函数调用。但即使是 -Og 也让它们内联,也不应该把你的调试弄得一团糟。无论如何,如果您在编译时完全不进行优化,那么您的整个程序将非常低效,以至于调用 htons 的额外指令肯定是您最不担心的。