Bug 109516 - warning: format '%lx' expects argument of type 'long unsigned int', but argument 2 has type 'const long unsigned int:48' [-Wformat=]
Summary: warning: format '%lx' expects argument of type 'long unsigned int', but argum...
Status: RESOLVED INVALID
Alias: None
Product: gcc
Classification: Unclassified
Component: c (show other bugs)
Version: 13.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2023-04-14 19:25 UTC by Yann Droneaud
Modified: 2023-04-14 21:05 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Yann Droneaud 2023-04-14 19:25:25 UTC
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).
Comment 1 Andrew Pinski 2023-04-14 19:35:40 UTC
>(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.
Comment 2 Andrew Pinski 2023-04-14 19:38:24 UTC
Note C++ does not allow implementation defined behavior here, the type is always the same across all implementations.
Comment 3 Andrew Pinski 2023-04-14 19:40:14 UTC
https://www.open-std.org/jtc1/sc22/WG14/www/docs/dr_120.html
is the defect report which makes this implementation defined really.
Comment 4 Yann Droneaud 2023-04-14 20:11:38 UTC
(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.
Comment 5 Andrew Pinski 2023-04-14 20:27:15 UTC
(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.
Comment 6 Andrew Pinski 2023-04-14 20:42:13 UTC
(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.
Comment 7 Yann Droneaud 2023-04-14 21:02:19 UTC
(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.
Comment 8 Andrew Pinski 2023-04-14 21:05:18 UTC
(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.