5

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.

Box Box Box Box
  • 5,094
  • 10
  • 49
  • 67
  • 2
    The format `%lu` is for an `unsigned long int` value only, as the compiler tells you. For an `unsigned long long int` value you need the format specifier `%llu`. The section of the standard you have in the image is about the integer *suffixes*, e.g. `1ul` to make an `unsigned long` value, not about the `printf` formats. – Some programmer dude Jan 25 '23 at 07:10
  • @Someprogrammerdude that is not the issue, my issue is with the 1st warning. Also, on my system `long` and `long long` both are 8 bytes wide, so I guess it does not make a difference whether I use `long` or `long long`. – Box Box Box Box Jan 25 '23 at 07:12
  • 1
    GCC does not support 128-bit integer literal: https://stackoverflow.com/questions/60860827/gcc-7-3-128-bit-unsigned-integer-operation How to print 128-bit integer: https://stackoverflow.com/questions/11656241/how-to-print-uint128-t-number-using-gcc – VLL Jan 25 '23 at 07:13
  • 3
    Now that you mention it, this compiler warning seems like a bug. A conforming compiler should not pick an unsigned type for a signed decimal constant. Both gcc and clang seem to be non-conforming, actually. It's BC (Before Coffee) still for me though, so I dare not yell compiler bug just yet. – Lundin Jan 25 '23 at 07:14
  • You still have *undefined behavior* because you use the wrong `printf` format. – Some programmer dude Jan 25 '23 at 07:16
  • @Someprogrammerdude I have posted a clarification completely removing the `printf` stuff. Please have a look. – Box Box Box Box Jan 25 '23 at 07:22

1 Answers1

5

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.

Lundin
  • 195,001
  • 40
  • 254
  • 396