Created attachment 49583 [details] source file arm-none-eabi-gcc: when compiling printf("%i\n", -2147483648); printf("%i\n", (int)-2147483648); INT_MIN in the first call gets recognized as a 64 bit argument and split across r2 and r3. r1 remains untouched. In the second call, INT_MIN is correctly put into r1: 8: e3a02102 mov r2, #-2147483648 ; 0x80000000 c: e3e03000 mvn r3, #0 10: e59f0010 ldr r0, [pc, #16] ; 28 <start_kernel+0x28> 14: ebfffffe bl 0 <printf> 18: e3a01102 mov r1, #-2147483648 ; 0x80000000 1c: e59f0004 ldr r0, [pc, #4] ; 28 <start_kernel+0x28> 20: ebfffffe bl 0 <printf> Source file attached was compiled with arm-none-eabi-gcc -v -save-temps -c start.c -o start.o gcc 10.2.0 was configured with --target=arm-none-eabi --prefix=$(PREFIX) --enable-interwork --enable-languages="c" --with-newlib --without-headers --disable-nls
Created attachment 49584 [details] preprocessed source
You need to write -2147483647 - 1 to make it 'int', -2147483648 are two tokens and the second is too large for int.
Compiling with -Wall should issue a warning pointing out the problem: $ cat pr97884.c && gcc -O2 -S -Wall pr97884.c void f (void) { __builtin_printf ("%i\n", -2147483648); __builtin_printf ("%i\n", (int)-2147483648); } pr97884.c: In function 'f': pr97884.c:3:23: warning: format '%i' expects argument of type 'int', but argument 2 has type 'long long int' [-Wformat=] 3 | __builtin_printf ("%i\n", -2147483648); | ~^ ~~~~~~~~~~~ | | | | int long long int | %lli
I am aware of the warning, I disagree with it's content. INT_MIN is an int, not a long long int. I understand why it is processed as a long long int internally, but that should not be visible from the outside world, at least imho.
But INT_MIN in C is (-2147483647 - 1) for 32-bit ints, not -2147483648, as has been explained, there is a significant difference for those two, because 2147483648 constant is not representable in int and therefore the constant gets a different type and the negation preserves that type. If something defines INT_MIN to -2147483648, the bug is in whatever defines it that way.
The C standard says "The type of an integer constant is the first of the corresponding list in which its value can be represented." The corresponding list for decimal constants with no suffix is int, long int, long long int. Since 2147483647 doesn't fit in int, it will have a larger type (long long int for your target). The unary - operator changes the value, but it still has type long long int. If you use INT_MIN from <limits.h> it will work, because that's defined as (-INT_MAX - 1) as Richard and Jakub said. It's defined like that for a good reason.
I do understand that +2147483648 is not an int. I am aware of how the 2s complement works. It seems to me the reason for INT_MIN being '(-2147483647 - 1)' instead of the mathematically equivalent '-2147483648' is the parser tokenizing the absolute value of the literal split from the sign of the literal. I'm also able to imagine why that eases parsing. But if splitting absolute value and sign - why not treat the absolute value as unsigned? Or maybe do a check 'in the end' (I have no knowledge of the codebase here...) whether one can reduce the size of the literal again? The fact is INT_MIN and '-2147483648' are both integers perfectly representable in 32 bits. I understand why gcc treats the second one differently (and clang does too) - I just think it's not right (or expectable for that matter). And if it's right, gcc should maybe warn about a 32bit literal being expanded to a larger type - not only in format strings. > The type of an integer constant is the first of the corresponding list in which its value can be represented. These kind of sentences make me think gcc's behaviour is wrong. The number can be represented in 32 bits.
If you design your own programming language, you can define it whatever way you want, but for C and C++ it is well defined how the compiler must behave in these cases, that -2147483648 are two separate tokens, unary minus and an integral constant.
(In reply to s.bauroth from comment #7) > > The type of an integer constant is the first of the corresponding list > > in which its value can be represented. > These kind of sentences make me think gcc's behaviour is wrong. The number > can be represented in 32 bits. No it can't. A few paragraphs above the text I quoted last time it says:L "An integer constant begins with a digit, but has no period or exponent part. It may have a prefix that specifies its base and a suffix that specifies its type." So the - sign is not part of the constant. The constant is 2147483648 and that doesn't fit in 32 bits. So it's a 64-bit type, and then that gets negated. That has been explained several times now. "I don't understand C and I won't read the spec" is not a GCC bug.
(In reply to Jonathan Wakely from comment #9) > (In reply to s.bauroth from comment #7) > > > The type of an integer constant is the first of the corresponding list > > > in which its value can be represented. > > These kind of sentences make me think gcc's behaviour is wrong. The number > > can be represented in 32 bits. > So the - sign is not part of the constant. The constant is 2147483648 and > that doesn't fit in 32 bits. So it's a 64-bit type, and then that gets > negated. If the constant is not allowed to have a sign why try to press it into a signed type? I know it's the standard that does it - but does it make any sense? > That has been explained several times now. And I said multiple times that I understand the reasoning. > "I don't understand C and I won't read the spec" is not a GCC bug. Not going to comment. I'm not questioning your reading of the standard, I'm not saying gcc breaks the standard. From a programmers perspective it's not intuitive that 'INT_MIN', '-2147483647 - 1' and all the other forms (like int a = -2147483648; printf("%i", a);' work, but '-2147483648' does not. And from a technical perspective it is absolutely unnecessary. Despite what the standard says about how to tokenize the number , it fits in 32 bits. It just does.
2147483648 does not fit in 32 bits.