gcc 和 MSFT CL 之间的位域结构大小不同

Bitfield struct size different between gcc and MSFT CL

我有以下代码:

#include <cstdint>

#pragma pack(1)
using MyType_t = union {
    uint8_t buffer[16];
    struct {
        uint64_t  a         : 55;   
        uint64_t  b         : 24;   
        uint64_t  c         : 1;    
        uint64_t  d         : 48;   
    }fields;
};
#pragma pack()

int main()
{
    return sizeof(MyType_t);
}

我在 gcc\clang 和 Visual C++ (Microsoft CL) 之间得到不同的结果, 当我在 Compiler Explorer 中比较汇编代码时,我得到以下信息:

clang (-std=c++11 -O3)

main:                                   # @main
        mov     eax, 16
        ret

x86-64 gcc 6.3 (-O3)

main:
        mov     eax, 16
        ret

x86-64 CL 19 2017 RTW (-Ox)

main    PROC
        mov      eax, 24
        ret      0
main    ENDP

是 Visual C++ 编译器错误还是未定义行为?

我认为这是未定义的行为。 @NathanOliver 的答案是正确的:GCC 和 Clang 跨越两个 uint64_t 值。不过,当您阅读它时是有代价的:请参阅 Compiler Explorer 上的这个非常相似的代码示例,其中 GCC 现在必须读取两个字段并进行一些数学计算以给出第二个值。

如果你希望两个布局在两个编译器之间保持一致,你可以使用 GCC 的 __attribute__((ms_struct)) 指令让它使用微软的位域布局算法:

using MyType_t 
= union {
    uint8_t buffer[16];
    struct  __attribute__((ms_struct)) {
        uint64_t  a         : 55;   
        uint64_t  b         : 24;   
        uint64_t  c         : 1;    
        uint64_t  d         : 48;   
    }fields;
};

您还可以将 -mms-bitfields 选项与 GCC 一起使用,但这是一个 ABI 更改选项,可能会破坏其他代码。

如果您想走另一条路,并强制 Microsoft 的编译器使用 GCC 的位域布局,我认为没有任何属性或选项可以做到这一点。您必须更改代码并拆分 b 成员,这样它就不会跨越 64 位边界。类似于:

#pragma pack(1)
typedef union {
    uint8_t buffer[16];
#ifdef USE_GCC_BITFIELDS
    struct __attribute__((gcc_struct))  {
        uint64_t  a         : 55;   
        uint64_t  b         : 24;   
        uint64_t  c         : 1;    
        uint64_t  d         : 48;   
    }fields;
    uint64_t get_a() { return fields.a; }
    uint64_t get_b() { return fields.b; }
    uint64_t get_c() { return fields.c; }
    uint64_t get_d() { return fields.d; }
#elif defined(USE_MS_BITFIELDS)
    struct {
        uint64_t  a         : 55;   
        uint64_t  bl        : 9;
        uint64_t  bh        : 15;  
        uint64_t  c         : 1;    
        uint64_t  d         : 48;   
    }fields;
    uint64_t get_a() { return fields.a; }
    uint64_t get_b() { return fields.bl | (fields.bh << 9); }
    uint64_t get_c() { return fields.c; }
    uint64_t get_d() { return fields.d; }
#else /* portable code that should work anywhere */
    unsigned long long get_ull(int i) {
        typedef unsigned long long ull; unsigned char *p = buffer + i;
        return (ull) p[0] | ((ull) p[1] << 8) | ((ull) p[2] << 16) | ((ull) p[3] <<  24)
            | ((ull) p[4] << 32) | ((ull) p[5] << 40) | (((ull) p[6]) << 48)
            | ((ull) p[7] << 56); }
    unsigned long long get_a() { return get_ull(0) & ((1ULL << 55) - 1); }
    unsigned get_b() { return (buffer[6] >> 7) | (buffer[7] << 1) 
            | (buffer[8] << 9) | ((buffer[9] & 0x7F) << 17); }
    unsigned get_c() { return buffer[9] >> 7; }
    unsigned long long get_d() { return get_ull(8) >> 16; }
#endif

} MyType_t;
#pragma pack()