简体   繁体   中英

Bitfield struct size different between gcc and MSFT CL

I have the following code:

#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);
}

I'm getting different result between gcc\\clang and Visual C++ (Microsoft CL), when I compare the assembly code in Compiler Explorer and I got the following:

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

is it Visual C++ compiler bug or it is undefined behavior ?

I believe this is undefined behavior. @NathanOliver has the right answer: GCC and Clang are spanning the two uint64_t values. There's a cost when you read it though: see this very similar code sample on the Compiler Explorer where GCC now has to read two fields and do some math to give second value.

If you want the two layous to be consistent between the two compilers you can use GCC's __attribute__((ms_struct)) directive to have it use Microsoft's bitfield layout algorithm:

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;
};

You can also use the -mms-bitfields option with GCC, but this an ABI changing option that could break other code.

If you want to go the other way, and force Microsoft's compiler to use GCC's bitfield layout, I don't think there's any attribute or option to do that. You would have to change your code and split the b member so it doesn't cross the 64-bit boundary. Something like:

#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()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM