4

I'm seeing a strange (to me) difference in behavior between Clang and GCC when comparing an integer with its negation. Also, pre-v12 GCC behaves like Clang.

Code is below, but also here's a live link on godbolt.org.

int32_t x = 0x80000000U;
cout << " x: " << x << endl;
cout << "-x: " << -x << endl;
if ( x == -x) {
 cout << "equal" << endl;
} else {
 cout << "NOT equal" << endl;
}

For GCC versions before 12, and for modern Clang, this prints "equal"

For GCC 12+, this prints "NOT equal".

My question is: why?

It does not seem to be a bug, since all GCC versions from 12 on have this behavior. But it is clearly a change from historical behavior.

Is there a reason behind this change? What is actually going on in this code, in the two different cases?

My only guess is that somehow GCC is promoting x to a larger type, since 0x80000000 does not fit in int32_t, and using that for the comparison. But by what logic?

Note: even adding a int32_t(-x) cast inside the comparison does not change the behavior.

asked Aug 29, 2025 at 17:21
8
  • 7
    Negating an int32_t with a value of 0x80000000u is undefined behavior Commented Aug 29, 2025 at 17:32
  • @asimes: I see now... Is assigning 0x80000000u initially undefined behavior? (it seems to reliably result in -2147483648). Or is it just the negation of that INT_MIN value, later on? Commented Aug 29, 2025 at 17:42
  • 1
    The compiler is free to do whatever it wants with undefined behavior and signed integer overflow is undefined behavior. Maybe GCC changed how they handle signed integer overflow at some point but reasoning about how undefined behavior should behave isn't productive Commented Aug 29, 2025 at 17:46
  • @asimes Hmm. That looks like a missing case in C++ standard. It says "the result is the negative of the operand", no exceptions. But that's impossible if the negation value doesn't fit in the result type. Commented Aug 29, 2025 at 17:46
  • 2
    @Yksisarvinen: It's covered by expr.pre p4: "If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined." Commented Aug 31, 2025 at 14:07

2 Answers 2

10

This self-answer is just to spell things out, for future reference.

First, there is an assignment of a too-large unsigned value to a signed type:

int32_t x = 0x80000000U;

Before C++20 (see @shananton's comment), that is generally "implementation defined" behavior. Both Clang and GCC (all versions) perform the expected two's-compliment wraparound, here — but that's not guaranteed by the standard, before C++20.

So, x gets a value of -2147483648 (which is INT_MIN). You see this in the printout.

Then, the comparison of x == -x is made. But -x is undefined, for INT_MIN, since the result will not fit in the signed type.

With undefined behavior, the compiler is free to assume it can never happen. So, in this case, GCC optimizes away the path where they might be equal, since that undefined behavior is "not allowed" to happen.

This behavior can be altered with -fno-strict-overflow (which disables such optimizations), or with the even stronger -fwrapv, which makes signed integer overflow fully-defined.

answered Aug 29, 2025 at 17:51
Sign up to request clarification or add additional context in comments.

3 Comments

Actually, unsigned to signed conversions like this became well-defined in C++20, and are now guaranteed to result in "the unique value of the destination type that is congruent to the source integer modulo 2^N , where N is the width of the destination type" (which is what you would normally expect). This is because, starting from C++20, integers are required to use two's complement for the representation
Ah thanks — I updated it to make note of C++20.
Signed overflow due to arithmetic is, of course, still undefined behavior
2

In your code -x is undefined behavior. Some compilers give an error or warning by changing x to be constexpr:

#include <iostream>
#include <cstdint>
int main() {
 constexpr std::int32_t x = 0x80000000u;
 std::cout << -x << std::endl;
 return 0;
}

My compiler gives a warning, looks like your godbolt example produces an error when constexpr is added

answered Aug 29, 2025 at 17:43

1 Comment

make it constexpr std::int32_t y = -x; afaik then there must be an error. godbolt.org/z/rEP1rxM98

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.