Consider the following code:
#include <stdio.h>
int main(void) {
printf("%llu\n", 18446744073709551615);
printf("%llu\n", 18446744073709551615ULL);
return 0;
}
Upon compilation (gcc -std=c18
), I get the following warnings:
test1.c: In function ‘main’:
test1.c:4:26: warning: integer constant is so large that it is unsigned
4 | printf("%llu\n", 18446744073709551615);
| ^~~~~~~~~~~~~~~~~~~~
test1.c:4:20: warning: format ‘%llu’ expects argument of type ‘long long unsigned int’, but argument 2 has type ‘__int128’ [-Wformat=]
4 | printf("%llu\n", 18446744073709551615);
| ~~~^ ~~~~~~~~~~~~~~~~~~~~
| | |
| | __int128
| long long unsigned int
If an integer constant cannot be represented by any type in its list, it may have an extended integer type, if the extended integer type can represent its value. If all of the types in the list for the constant are signed, the extended integer type shall be signed. If all of the types in the list for the constant are unsigned, the extended integer type shall be unsigned. If the list contains both signed and unsigned types, the extended integer type may be signed or unsigned. If an integer constant cannot be represented by any type in its list and has no extended integer type, then the integer constant has no type.
From the above it is clear that as ULONG_MAX
cannot fit in int
, long int
and long long int
, the compiler will try the signed extended integer types; as ULONG_MAX
does fit inside __int128
, that then becomes the type of the integer constant, as can be seen from the second warning message.
This is all expected behavior, but the issue I am facing is that clearly __int128
is a signed type, as is expected from the C standard. But then why does the first warning message ("integer constant is so large that it is unsigned") say that the constant is treated as unsigned? That makes no sense to me, as according to 6.4.4.1.6 only the signed extended integer types are checked, so how is the integer constant being treated as unsigned?
To clarify the question a bit, my issue is not with printf
; the format warning is expected, I just left it there to show that the constant is of type __int128
.
Consider the code:
#include <stdio.h>
int main(void) {
__int128 a = 18446744073709551615;
unsigned long long b = 18446744073709551615;
return 0;
}
Compiling this gives the warning:
test2.c: In function ‘main’:
test2.c:4:22: warning: integer constant is so large that it is unsigned
4 | __int128 a = 18446744073709551615;
| ^~~~~~~~~~~~~~~~~~~~
test2.c:5:32: warning: integer constant is so large that it is unsigned
5 | unsigned long long b = 18446744073709551615;
| ^~~~~~~~~~~~~~~~~~~~
My issue is that since the constant is of type __int128
, why does the compiler say it is unsigned? Obviously __int128
is a signed type.
CodePudding user response:
gcc (and clang) gives a diagnostic message so it is conforming, in the strict sense that it doesn't have to support extended integer types and that it gave some sort of diagnostic message ("warning: bleh" would have made them equally conforming).
This is however a minor compiler bug, since decimal integer constants use the the quoted list in 6.4.4.1: int
then long
then long long
. Therefore this applies: "If all of the types in the list for the constant are signed, the extended integer type shall be signed."
gcc 12.2 does behave like that too, as we can see from this demo:
#include <stdio.h>
int main (void)
{
_Generic(18446744073709551615,
long long: puts("long long"),
unsigned long long: puts("unsigned long long"),
__int128_t: puts("(signed) __int128_t"),
default: puts("some extended type") );
typeof(18446744073709551615) x = -1;
printf("Value: %d Size: %zu\n", (int)x, sizeof(x));
}
Output:
(signed) __int128_t
-1
Had the integer constant been "so large that it is unsigned", then _Generic would have printed "unsigned long long" or alternatively "some extended type". Similarly, x
would have gotten a positive value during signed to unsigned conversion.
Conclusion: gcc picks the correct type but the warning message is incorrect. It should say something like "integer constant is so large that it is extended".
I would guess this message is some remain from C90 where extended integer types didn't exist. Compiling with -std=c90
adds an additional warning:
warning: this decimal constant is unsigned only in ISO C90
It would appear that this is the correct warning that should always be displayed. Seems to be a minor bug that occurred during the switch from gnu90 to gnu11 as default option for gcc.