0

I want to know if x - y overflows.

Below is my code.

#include <stdio.h>

/* Determine whether arguments can be subtracted without overflow */
int tsub_ok(int x, int y)
{
    return (y <= 0 && x - y >= x) || (y >= 0 && x - y <= x);
}

int main()
{
    printf("0x80000000 - 1 : %d\n", tsub_ok(0x80000000, 1));
}

Why can't I get the result I expect?

user438383
  • 5,716
  • 8
  • 28
  • 43
Bralow Qin
  • 61
  • 4
  • 6
    Typical `int` (32-bit long) cannot store the value `0x80000000`. – MikeCAT Aug 10 '21 at 15:30
  • 1
    Max value for a signed two's-complement 32 bit integer is `2147483647`, or `7FFFFFFF` – NathanOliver Aug 10 '21 at 15:33
  • If you wanted to have only the most significant bit (i.e. sign) set, you could do `unsigned int val = 0x80000000`, and then do `int signedval = * (int*) &val`, but you should just use one of the predefined macros like `INT32_MIN`. Also signed integer overflow is UB so you can't really test for it, unless you know how the compiler/architecture/processor handles this – Lala5th Aug 10 '21 at 15:33
  • 4
    The reason you're not getting what you expect, is basically because [signed integer overflow results in undefined behavior](https://stackoverflow.com/questions/18195715/why-is-unsigned-integer-overflow-defined-behavior-but-signed-integer-overflow-is). Anything at all can happen after it, and your compiler does something contrary to what you thought would happen. Other compilers (or even the same compiler with different settings or optimization options) might behave according to your expectations. But the main point is you can't rely on any specific behavior here. – Sander De Dycker Aug 10 '21 at 15:52
  • Try compiling with `clang -fsanitize=undefined`, and you will get a nice message at runtime. – Marc Glisse Aug 10 '21 at 16:00
  • @12431234123412341234123 Well integer overflow is UB already, and I am not assigning anything. I am telling the program to interpret bytes at a specific location as an `int`. Yes the value of the `unsigned int` there is more than the max value of `int`, but the bytes are still valid as `int`, just interpreted differently – Lala5th Aug 10 '21 at 17:49
  • @Lala5th That would cause UB when `INT_MAX<=0x7FFFFFFF`. You should not interpret a `unsigned int` object as a `int` when the value is out of range of an `int`. – 12431234123412341234123 Aug 10 '21 at 17:51
  • 1
    @Lala5th Yes, the Program already has UB. But that does not change the fact that your suggestion also causes UB. _but the bytes are still valid as int, just interpreted differently_ No, that causes UB. It may work in practice but it is still UB. – 12431234123412341234123 Aug 10 '21 at 17:53
  • @Lala5th The link is about C++ not about C. It would not be UB when the value is in the range of both, the `unsigned` and signed one. Or when you assign it directly (e.g. `unsigned t=INT_MAX; t++; int i=t`) without any pointer operation (In this case it would be implementation defined and may raise a signal). – 12431234123412341234123 Aug 10 '21 at 20:05
  • @12431234123412341234123 Wrong link. this has the same thing but for both C and C++ https://gist.github.com/shafik/848ae25ee209f698763cffee272a58f8 – Lala5th Aug 10 '21 at 20:07
  • @Lala5th I don't find any mentioning in that link about accessing an unsigned integer with a pointer to the corresponding signed integer when the unsigned integer value is out of range for the signed type. It isn't UB when the value is still in range of the signed type, i agree, but not when it is outside of that range. There is als no UB in the other direction: accessing a signed integer with a pointer for the corresponding unsigned integer, even when the value is negative. – 12431234123412341234123 Aug 10 '21 at 20:21
  • @12431234123412341234123 But it is not "out of range". The only thing is that `unsigned int` and `int` interprets `0x80000000` from memory in different ways. If the right side was a literal, then yes that is UB, but here we are dereferencing a pointer to some memory address. As the cast is not UB (as per standard), the only thing that can be UB is the dereference, but as it is not UB to dereference negative integers, that surely cannot be the case – Lala5th Aug 10 '21 at 20:30
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/235851/discussion-between-12431234123412341234123-and-lala5th). – 12431234123412341234123 Aug 10 '21 at 20:35

4 Answers4

5

You can't check for overflow of signed integers by performing the offending operation and seeing if the result wraps around.

First, the value 0x80000000 passed to the function is outside the range of a 32 bit int. So it undergoes an implementation defined conversion. On most systems that use 2's compliment, this will result in the value with that representation which is -2147483648 which also happens to be the value of INT_MIN.

Then you attempt to execute x - y which results in signed integer overflow which triggers undefined behavior, giving you an unexpected result.

The proper way to handle this is to perform some algebra to ensure the overflow does not happen.

If x and y have the same sign then subtracting won't overflow.

If the signs differ and x is positive, one might naively try this:

INT_MAX >= x - y

But this could overflow. Instead change it to the mathematically equivalent:

INT_MAX + y >= x

Because y is negative, INT_MAX + y doesn't overflow.

A similar check can be done when x is negative with INT_MIN. The full check:

if (x>=0 && y>=0) {
    return 1;
} else if (x<=0 && y<=0) {
    return 1;
} else if (x>=0 && INT_MAX + y >= x) {
    return 1;
} else if (x<0 && INT_MIN + y <= x) {
    return 1;
} else {
    return 0;
}
dbush
  • 205,898
  • 23
  • 218
  • 273
1

Yes, x - y overflows.

We assume int and unsigned int are 32 bits in the C implementation you are using, as indicated in the title, and that two’s complement is used for int. Then the range of values for int is −231 to +231−1.

In tsub_ok(0x80000000, 1), the constant 0x80000000 has the value 231, and its type is unsigned int since it will not fit in an int. Then this value is passed to tsub_ok. Since the first parameter of tsub_ok has type int, the value is converted to int.

By C 2018 6.3.1.3 3, the conversion is implementation-defined. Many C implementations “wrap” the value modulo 232. Assuming your C implementation does this, the result of converting 231 to int is −231.

Then, inside the function, x - y is −231 − 1, and the result of that overflows the int type. The C standard does not define the behavior of the program when signed integer overflow occurs, and so any test that relies on comparing x - y when it may overflow is not supported by the C standard.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
0

Here an int is 32 bits. This means it has a total range of 2^32 possible values. Converting this to hex, that's a max of 0xFFFFFFFF(when unsigned), but not signed. A signed int will have a max hex value of 0x7FFFFFFF. Thus, you cannot store 0x80000000 in an int here and have everything work.

BTables
  • 4,413
  • 2
  • 11
  • 30
0

In computer programming, signed and unsigned numbers are represented only as sequences of bits. Bit 31 is the sign bit for a 32-bit signed int, hence the highest 32-bit int you can store is 0x7FFFFFFF, hence the overflow with 0x80000000 as signed int.
Remember, a signed int is an integer that can be both positive and negative. This is as opposed to an unsigned int, which can only be used to hold a positive integer. What you are trying to do is, you are trying a signed int variable hold an unsigned value - which causes the overflow.

For more info check Signed number representations or refer any beginner level digital number systems and programming book.

WedaPashi
  • 3,561
  • 26
  • 42