I've discovered today an unexpected format related warning when asking to print a bitfield over unsigned long: $ cat > fmt-bitfield.c <<EOF #include <inttypes.h> #include <stdint.h> #include <stdio.h> struct struct24 { uint32_t uint24:24; }; void print24(const struct struct24 *s) { printf("%" PRIx32 "\n", s->uint24); } struct struct48 { uint64_t uint48:48; }; void print48(const struct struct48 *s) { printf("%" PRIx64 "\n", s->uint48); } EOF $ gcc -Wformat -c fmt-bitfield.c fmt-bitfield.c: In function 'print48': fmt-bitfield.c:13:10: warning: format '%lx' expects argument of type 'long unsigned int', but argument 2 has type 'long unsigned int:48' [-Wformat=] 13 | { printf("%" PRIx64 "\n", s->uint48); } | ^~~ ~~~~~~~~~ | | | long unsigned int:48 In file included from fmt-bitfield.c:1: /usr/include/inttypes.h:121:41: note: format string is defined here 121 | # define PRIx64 __PRI64_PREFIX "x" See https://godbolt.org/z/r44e5rfnh I understood that, according to C standard, bitfield over type other that int or bool is an implementation defined behavior. But GCC is expected to support bitfield over long as well: https://gcc.gnu.org/onlinedocs/gcc/Structures-unions-enumerations-and-bit-fields-implementation.html - Allowable bit-field types other than _Bool, signed int, and unsigned int (C99 and C11 6.7.2.1). Other integer types, such as long int, and enumerated types are permitted even in strictly conforming mode. So I believe the warning is spurious, and formatting a value whose type is a bitfield over long should not trigger the warning when using %l format modifier. (For the record, clang doesn't report such warning).
>(For the record, clang doesn't report such warning). That is because there is another implement defined part of the C spec dealing with bitfields and GCC and clang decided to implement this differently., this is mentioned here: https://gcc.gnu.org/legacy-ml/gcc/2017-10/msg00192.html Basically GCC decided that the type of the bitfield uint48 has a type of unsigned:48 and since it is larger size than int, it does not get promoted to int and in this cases gets passed to the variadic function as that type. While clang decided that the type is still unsigned long long. Both behaviors are allowed by the C standard even.
Note C++ does not allow implementation defined behavior here, the type is always the same across all implementations.
https://www.open-std.org/jtc1/sc22/WG14/www/docs/dr_120.html is the defect report which makes this implementation defined really.
(In reply to Andrew Pinski from comment #1) > > Basically GCC decided that the type of the bitfield uint48 has a type of > unsigned:48 and since it is larger size than int, it does not get promoted > to int and in this cases gets passed to the variadic function as that type. I wouldn't have expected bitfield type to be "visible" outside of its structure. And I failed to comprehend how unsigned long int:48 can be passed to a variadic function without being promoted to plain unsigned long int ... Anyway, I will have to add a cast to silence the warning. > While clang decided that the type is still unsigned long long. AFAICT, there's no unsigned long long involved in my example.
(In reply to Yann Droneaud from comment #4) > > > While clang decided that the type is still unsigned long long. > > AFAICT, there's no unsigned long long involved in my example. Sorry unsigned long. Similar thing.
(In reply to Yann Droneaud from comment #4) > (In reply to Andrew Pinski from comment #1) > > > > Basically GCC decided that the type of the bitfield uint48 has a type of > > unsigned:48 and since it is larger size than int, it does not get promoted > > to int and in this cases gets passed to the variadic function as that type. > > I wouldn't have expected bitfield type to be "visible" outside of its > structure. Yes I understand that, but C actually has the idea of that being a type. > > And I failed to comprehend how unsigned long int:48 can be passed to a > variadic function without being promoted to plain unsigned long int ... Oh yes, there is no way for an user to get the value back via va_arg really. But this is just how C has an implementation defined behavior here really.
(In reply to Andrew Pinski from comment #6) > > > > And I failed to comprehend how unsigned long int:48 can be passed to a > > variadic function without being promoted to plain unsigned long int ... > > Oh yes, there is no way for an user to get the value back via va_arg really. > But this is just how C has an implementation defined behavior here really. I understand, but can't refrain myself from thinking it's an unfortunate discrepancy having a warning when formatting the uint64_t:48 case but not for uint32_t:24 one. Anyway, thanks for all the explanation. Regards.
(In reply to Yann Droneaud from comment #7) > (In reply to Andrew Pinski from comment #6) > > > > > > And I failed to comprehend how unsigned long int:48 can be passed to a > > > variadic function without being promoted to plain unsigned long int ... > > > > Oh yes, there is no way for an user to get the value back via va_arg really. > > But this is just how C has an implementation defined behavior here really. > > I understand, but can't refrain myself from thinking it's an unfortunate > discrepancy having a warning when formatting the uint64_t:48 case but not > for uint32_t:24 one. Because 24 is smaller than CHAR_BIT*sizeof(int) so it gets prompoted to int when passing it.