cgccconstantsunsignedint128

Why does gcc give a warning saying that the constant is unsigned because it is too large, when it is of the type __int128 and is signed?


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

The C standard section 6.4.4.1.5 and 6.4.4.1.6 say:

The type of an integer constant is the first of the corresponding list in which its value can be represented.

enter image description here

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.


Solution

  • 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.