2

In the following code, x and y are int32_t variables. In this simplified example, they always differ by 1. When they span the int32_t overflow boundary (0x7FFFFFFF, the max 2's compliment 32-bit positive number, to 0x80000000, the largest magnitude negative number), subtracting them seems to give different results when it is done inside the conditional of the if statement (Method 1) than it does if the result is stored in a temporary variable (Method 2). Why don't they give the same result?

I would think that subtracting two int32_t variables would yield a result of type int32_t, so using a temporary of that type shouldn't change anything. I tried explicitly typecasting inside the if statement conditional; that didn't change anything. FWIW, Method 2 gives the result I would expect.

The code:

int32_t x = (0x80000000 - 3);

int i;
for( i = 0; i < 5; ++i )
{
    int32_t y = x + 1;      // this may cause rollover from 0x7fffffff (positive) to 0x80000000 (negative)

    UARTprintf("\n" "x = 0x%08X, y = 0x%08X", x, y );

    if( ( y - x ) >= 1 )            // Method 1
        UARTprintf(" - true ");
    else
        UARTprintf(" - FALSE");

    int32_t z = ( y - x );          // Method 2
    if( ( z ) >= 1 )
        UARTprintf(" - true ");
    else
        UARTprintf(" - false");

    ++x;
}

Output:

x = 0x7ffffffd, y = 0x7ffffffe - true  - true
x = 0x7ffffffe, y = 0x7fffffff - true  - true
x = 0x7fffffff, y = 0x80000000 - FALSE - true
x = 0x80000000, y = 0x80000001 - true  - true
x = 0x80000001, y = 0x80000002 - true  - true

In my actual application (not this simplified example), y is incremented by a hardware timer and x is a record of when some code was last executed. The test is intended to make some code run at intervals. Considering that y represents time and the application may run for a very long time before it is restarted, just not letting it overflow isn't an option.

Noting, as several of you did, that the standard does not define the behavior when signed integer overflow occurs tells me that I don't have a right to complain that I can't count on it working the way I want it to, but it doesn't give me a solution I can count on. Even using a temporary variable, which seems to work with my current compiler version and settings, might quit working when one of those things changes. Do I have any trustworthy options short of resorting to assembly code?

12
  • 8
    It would be fair to note that signed integer overflow leads to undefined behaviour. Commented Sep 20, 2017 at 22:43
  • Btw ideone.com/1uxeDp. Did you compile with optimisations? Commented Sep 20, 2017 at 22:45
  • Please clarify whether you are using C99 or C11 mode (as opposed to C89 mode or a proprietary mode, with a non-standard definition of int32_t) Commented Sep 20, 2017 at 22:49
  • 2
    As well as the overflow UB, you also cause UB by using the wrong format specifier in printf (assuming UARTprintf is a macro for a standard printf function, or ultimately does va_arg(ap, unsigned int)) Commented Sep 20, 2017 at 22:50
  • 1
    @Olaf I doubt OP read and followed the tag wiki policies in selecting a tag Commented Sep 20, 2017 at 23:31

2 Answers 2

2

Given that signed integer overflow leads to undefined behaviour - you better not try to explain it.

Because your assumptions are based on "common sense", not the standard.

Otherwise - check assembly and try to debug it, but again, the outcome would not be scalable: you won't be able to apply the new knowledge to some other case (but with no doubt it would be fun to do).

Sign up to request clarification or add additional context in comments.

Comments

0

The question I didn't know enough to ask originally is, "How can I avoid undefined behavior when doing subtraction on integers that might have overflowed?" Please correct me if I am wrong, but it appears that the answer to that question would be "use unsigned rather than signed integers" because the results are well defined (per C11 6.2.5/9) "a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type."

In this case, that is enough to come up with a working solution because the elapsed time will always be zero or a small positive number therefore the result of the subtraction will always be positive. So the result of the subtraction could be kept as an unsigned number and compared ( ">=1" ) or converted back to a signed int for comparison (C11 6.3.1.3 "When a value with integer type is converted to another integer type ... if the value can be represented by the new type, it is unchanged." This code works, and I believe does not rely on any undefined behavior: "if( ( (int32_t)(uint32_t)y - (uint32_t)x ) >= 1 )"

In the more general case, however, converting to unsigned to do the subtraction, then converting back to a signed result (which might be negative) is not well defined. C11 6.3.1.3 says regarding converting to another integer type that if "the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised." So I can still imagine a scenario in which assembly code would be needed to achieve well-defined results; this just isn't one of them.

1 Comment

If using C++, it looks like a practical solution would be to use a library like SafeInt <safeint.codeplex.com>. In some cases it does "intermediate calculations...with unsigned numbers, and unsigned overflow is defined by the standard..." For example, "the compiler may remove -x, but it won't remove ~(unsigned)x + 1, which emits the same bit pattern (and the same assembly code)."

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.