found wrong data when compiled by gcc 9.2.0 (x86_64, -std=gnu11). Have not tried other gcc versions. The results are correct on clang 8.0.1. Long story short: some bits are missing when using bitfields with uint64_t as the declared type. Maybe I should use a (u64) cast on every dereference to those fields. But the results are not always wrong. Maybe I should read some C11 specs. But consider the different behaviors between gcc and clang, I decide to report a bug here in hope to get some better help. Thanks. This reproduces the results: gcc/clang -std=gnu11 main.c #include <stdint.h> #include <stdlib.h> #include <stdio.h> typedef uint64_t u64; struct st { union { struct { u64 v1:19; u64 v2:45; }; void * ptr; }; }; struct bits { u64 bit1:1; u64 bitx:63; }; int main(void) { char * val = "0xffff0000fff8"; // x has 48 valid bits, the three low bits are 0s volatile u64 x = strtoull(val, NULL, 16); printf("%lx\n", x); // st stores that 45 bits struct st st = {.v1 = 0, .v2 = (x >> 3)}; printf("%p\n", st.ptr); // y should get the original bits; but the three high bits are gone. u64 y = st.v2 << 3; printf("%lx\n", y); // this is another (minor) related case. // some are correct and some are wrong. struct bits b = {.bit1 = 1, .bitx = 0}; // this is more interesting. gcc shows "80000000", clang shows "1" printf("%lx %lx %lx\n", b.bit1, b.bit1 << 1, b.bit1 << 63); return 0; }
-W -Wall: t.c: In function ‘main’: t.c:40:3: warning: left shift count >= width of type [enabled by default] printf("%lx %lx %lx\n", b.bit1, b.bit1 << 1, b.bit1 << 63); ^ t.c:40:3: warning: format ‘%lx’ expects argument of type ‘long unsigned int’, but argument 2 has type ‘int’ [-Wformat=] t.c:40:3: warning: format ‘%lx’ expects argument of type ‘long unsigned int’, but argument 3 has type ‘int’ [-Wformat=] t.c:40:3: warning: format ‘%lx’ expects argument of type ‘long unsigned int’, but argument 4 has type ‘int’ [-Wformat=]
bit-fields in C is a type. while in C++, it is not. So: st.v2 << 3 This is not ((long)st.v2) << 3 but rather (long:45)<<3. So it gets truncated. C11: ffff0000fff8 0xffff0000fff80000 1fff0000fff8 1 2 ffffffff80000000 C++14: ffff0000fff8 0xffff0000fff80000 ffff0000fff8 1 2 ffffffff80000000
There is a defect report dealing with this in C and such. Both are correct for C11 but would be incorrect for C90 which defines bit-field types because of the defect report. GCC decided to follow the C90 way because it is easier to implement one way for both.