110

At first glance, this question may seem like a duplicate of How to detect integer overflow?, however it is actually significantly different.

I've found that while detecting an unsigned integer overflow is pretty trivial, detecting a signed overflow in C/C++ is actually more difficult than most people think.

The most obvious, yet naive, way to do it would be something like:

int add(int lhs, int rhs)
{
 int sum = lhs + rhs;
 if ((lhs >= 0 && sum < rhs) || (lhs < 0 && sum > rhs)) {
  /* an overflow has occurred */
  abort();
 }
 return sum; 
}

The problem with this is that according to the C standard, signed integer overflow is undefined behavior. In other words, according to the standard, as soon as you even cause a signed overflow, your program is just as invalid as if you dereferenced a null pointer. So you can't cause undefined behavior, and then try to detect the overflow after the fact, as in the above post-condition check example.

Even though the above check is likely to work on many compilers, you can't count on it. In fact, because the C standard says signed integer overflow is undefined, some compilers (like GCC) will optimize away the above check when optimization flags are set, because the compiler assumes a signed overflow is impossible. This totally breaks the attempt to check for overflow.

So, another possible way to check for overflow would be:

int add(int lhs, int rhs)
{
 if (lhs >= 0 && rhs >= 0) {
  if (INT_MAX - lhs <= rhs) {
   /* overflow has occurred */
   abort();
  }
 }
 else if (lhs < 0 && rhs < 0) {
  if (lhs <= INT_MIN - rhs) {
   /* overflow has occurred */
   abort();
  }
 }

 return lhs + rhs;
}

This seems more promising, since we don't actually add the two integers together until we make sure in advance that performing such an add will not result in overflow. Thus, we don't cause any undefined behavior.

However, this solution is unfortunately a lot less efficient than the initial solution, since you have to perform a subtract operation just to test if your addition operation will work. And even if you don't care about this (small) performance hit, I'm still not entirely convinced this solution is adequate. The expression lhs <= INT_MIN - rhs seems exactly like the sort of expression the compiler might optimize away, thinking that signed overflow is impossible.

So is there a better solution here? Something that is guaranteed to 1) not cause undefined behavior, and 2) not provide the compiler with an opportunity to optimize away overflow checks? I was thinking there might be some way to do it by casting both operands to unsigned, and performing checks by rolling your own two's-complement arithmetic, but I'm not really sure how to do that.

17
  • 1
    Rather then attempting to detect, isn't it better pursuit to write code which doesn't have possibility of overflow? Commented Oct 15, 2010 at 17:35
  • 10
    @ArunSaha: It's really hard to take calculations and ensure that they will not overflow, and it's impossible to prove in the general case. The usual practice is to use as wide an integer type as possible and hope. Commented Oct 15, 2010 at 18:00
  • 9
    @Amardeep: Dereferencing a null pointer is equally undefined as signed overflow. Undefined behavior means that, as far as the Standard goes, anything can happen. One can't assume that the system won't be in an invalid and unstable state after signed overflow. The OP pointed out one consequence of this: it's perfectly legal for the optimizer to remove code that detects signed overflow once it happens. Commented Oct 15, 2010 at 18:02
  • 20
    @Amardeep: I mentioned such an implementation. GCC will remove the overflow check code when optimization flags are set. So it will basically break your program. This is arguably worse than a null pointer dereference, since it can result in subtle security flaws, whereas dereferencing a null will likely just bluntly clobber your program with a segfault. Commented Oct 15, 2010 at 18:36
  • 2
    @Amardeep: I've certainly seem implementations where, depending upon compiler settings, overflow would cause a trap. It would be nice if languages allowed one to specify whether particular unsigned variables or quantities should (1) wrap cleanly, (2) fault, or (3) do whatever is convenient. Note that if a variable is smaller than a machine's register size, requiring that unsigned quantities wrap cleanly may prevent generation of optimal code. Commented Oct 15, 2010 at 20:52

14 Answers 14

52
+50

No, your 2nd code isn't correct, but you are close: if you set

int half = INT_MAX/2;
int half1 = half + 1;

the result of an addition is INT_MAX. (INT_MAX is always an odd number). So this is valid input. But in your routine you will have INT_MAX - half == half1 and you would abort. A false positive.

This error can be repaired by putting < instead of <= in both checks.

But then also your code isn't optimal. The following would do:

int add(int lhs, int rhs)
{
 if (lhs >= 0) {
  if (INT_MAX - lhs < rhs) {
   /* would overflow */
   abort();
  }
 }
 else {
  if (rhs < INT_MIN - lhs) {
   /* would overflow */
   abort();
  }
 }
 return lhs + rhs;
}

To see that this is valid, you have to symbolically add lhs on both sides of the inequalities, and this gives you exactly the arithmetical conditions that your result is out of bounds.

Note in 2023: C23 will have the <stdckdint.h> header that implements such overflow checks in the same way as the gcc builtins that are mentioned in other answers.

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

2 Comments

a small optimization you could do, I suppose this would depend on your hardware, I'm not sure which is better, but if you use an if and an else if with (lhs > 0 && rhs > 0) and (lhs < 0 && rhs < 0) this would allow you to skip making a subtraction in cases where the signs do not match or either value is 0, but it would require 4 comparisons in those cases and it would require an extra comparison in the case that both values are negative. Which are faster in the hardware? comparisons or arithmetic operations such as subtractions?
@Dyskord just write it in the way that is easiest to understand and let the compiler figure that out for you.
29

Your approach with subtraction is correct and well-defined. A compiler cannot optimize it away.

Another correct approach, if you have a larger integer type available, is to perform the arithmetic in the larger type and then check that the result fits in the smaller type when converting it back

int sum(int a, int b)
{
    long long c;
    assert(LLONG_MAX>INT_MAX);
    c = (long long)a + b;
    if (c < INT_MIN || c > INT_MAX) abort();
    return c;
}

A good compiler should convert the entire addition and if statement into an int-sized addition and a single conditional jump-on-overflow and never actually perform the larger addition.

Edit: As Stephen pointed out, I'm having trouble getting a (not-so-good) compiler, gcc, to generate the sane asm. The code it generates is not terribly slow, but certainly suboptimal. If anyone knows variants on this code that will get gcc to do the right thing, I'd love to see them.

16 Comments

Then, by your definition gcc 4.4.4 is not a good compiler. I just tried this at -O2 and -O3 and got the same code, 12 lines of assembly (not counting assembler commands) where the actual addition is performed by leaq (%rax,%rcx), %rcx, or addl %eax, %ecx; adcl %edx, %ebx if the -m32 flag is given.
Out of curiosity, have you been successful in getting a compiler to do this optimization? A quick test against a few compilers didn't turn up any that could do it.
On x86_64, there's nothing inefficient about using 32-bit integers. Performance is identical to 64-bit ones. One motivation for using smaller-than-native-word-size types is that it's extremely efficient to handle overflow conditions or carry (for arbitrary-precision arithmetic) since the overflow/carry happens in a directly-accessible location.
@R., @Steven: no the subtraction code that the OP gave is not correct, please see my answer. I also give a code there that just does it with at most two comparisons. Perhaps the compilers will do better with that.
This approach does not work in the uncommon platform where sizeof(long long) == sizeof(int). C specifies only that sizeof(long long) >= sizeof(int).
|
29

The fastest possible way is to use the GCC builtin:

int add(int lhs, int rhs) {
    int sum;
    if (__builtin_add_overflow(lhs, rhs, &sum))
        abort();
    return sum;
}

On x86, GCC compiles this into:

    mov %edi, %eax
    add %esi, %eax
    jo call_abort 
    ret
call_abort:
    call abort

which uses the processor's built-in overflow detection.

If you're not OK with using GCC builtins, the next fastest way is to use bit operations on the sign bits. Signed overflow in addition occurs when:

  • the two operands have the same sign, and
  • the result has a different sign than the operands.

The sign bit of ~(lhs ^ rhs) is on iff the operands have the same sign, and the sign bit of lhs ^ sum is on iff the result has a different sign than the operands. So you can do the addition in unsigned form to avoid undefined behavior, and then use the sign bit of ~(lhs ^ rhs) & (lhs ^ sum):

int add(int lhs, int rhs) {
    unsigned sum = (unsigned) lhs + (unsigned) rhs;
    if ((~(lhs ^ rhs) & (lhs ^ sum)) & 0x80000000)
        abort();
    return (int) sum;
}

This compiles into:

    lea (%rsi,%rdi), %eax
    xor %edi, %esi
    not %esi
    xor %eax, %edi
    test %edi, %esi
    js call_abort
    ret
call_abort:
    call abort

which is quite a lot faster than casting to a 64-bit type on a 32-bit machine (with gcc):

    push %ebx
    mov 12(%esp), %ecx
    mov 8(%esp), %eax
    mov %ecx, %ebx
    sar $31, %ebx
    clt
    add %ecx, %eax
    adc %ebx, %edx
    mov %eax, %ecx
    add $-2147483648, %ecx
    mov %edx, %ebx
    adc $0, %ebx
    cmp $0, %ebx
    ja call_abort
    pop %ebx
    ret
call_abort:
    call abort

3 Comments

The bitwise operations make assumptions on the size and representation of int that restrict their portability. That should at least be mentioned.
@TobySpeight I don't know about C, but I believe the latest C++ spec mandates the use of two's complement. That should help.
That's true: I guess we could use a __cplusplus version guard and write the constant as ~0u - ~0u / 2 to make it adapt to the integer width.
23

For the gcc case, from gcc 5.0 Release notes we can see it now provides a __builtin_add_overflow for checking overflow in addition:

A new set of built-in functions for arithmetics with overflow checking has been added: __builtin_add_overflow, __builtin_sub_overflow and __builtin_mul_overflow and for compatibility with clang also other variants. These builtins have two integral arguments (which don't need to have the same type), the arguments are extended to infinite precision signed type, +, - or * is performed on those, and the result is stored in an integer variable pointed to by the last argument. If the stored value is equal to the infinite precision result, the built-in functions return false, otherwise true. The type of the integer variable that will hold the result can be different from the types of the first two arguments.

For example:

__builtin_add_overflow( rhs, lhs, &result )

We can see from the gcc document Built-in Functions to Perform Arithmetic with Overflow Checking that:

[...]these built-in functions have fully defined behavior for all argument values.

clang also provides a set of checked arithmetic builtins:

Clang provides a set of builtins that implement checked arithmetic for security critical applications in a manner that is fast and easily expressable in C.

in this case the builtin would be:

__builtin_sadd_overflow( rhs, lhs, &result )

5 Comments

This function appears to be very useful except for one thing: int result; __builtin_add_overflow(INT_MAX, 1, &result); does not explicitly say what is stored in result on overflow and unfortunately is quiet on specifying undefined behavior does not occur. Certainly that was the intent - no UB. Better if it specified that.
@chux good point, it states here the result is always defined, I updated my answer. It would be rather ironic if that was not the case.
Interesting your new reference does not have a (unsigned) long long *result for __builtin_(s/u)addll_overflow. Certainly these are in error. Makes one wonder as to the veracity of other aspects. IAC, good to see these __builtin_add/sub/mull_overflow(). Hope they make it to the C spec someday.
+1 this generates much better assembly than anything you could get in standard C, at least not without relying on your compiler's optimizer to figure out what you're doing. One should detect when such builtins are available and only use a standard solution when the compiler doesn't provide one.
Now that GCC supports <stdckint.h>, we can use standard C ckd_add() function if compiling against C23 or later, for exactly the same functionality (but different order of arguments).
18

IMHO, the eastiest way to deal with overflow sentsitive C++ code is to use SafeInt<T>. This is a cross platform C++ template hosted on code plex which provides the safety guarantees that you desire here.

I find it very intuitive to use as it provides the many of the same usage patterns as normal numerical opertations and expresses over and under flows via exceptions.

Comments

10

If you use inline assembler you can check the overflow flag. Another possibility is taht you can use a safeint datatype. I recommend that read this paper on Integer Security.

10 Comments

+1 This is another way of saying "If C won't define it, then you're forced into platform-specific behavior." So many things that are easily taken care of in assembly are undefined in C, creating mountains out of molehills in the name of portability.
I gave a downvote for an asm answer to a C question. As I have said, there are correct, portable ways to write the check in C which will generate the exact same asm that you would write by hand. Naturally if you use these, the performance impact will be the same, and it will be much less of an impact than the C++ safeint stuff you also recommended.
@Matthieu: If you are writing code that will only be used on one implementation, and that implementation guarantees that something will work, and you need good integer performance, you can certainly use implementation-specific tricks. That isn't what the OP was asking for, though.
C distinguishes implementation-defined behavior and undefined behavior for good reasons, and even if something with UB "works" in the current version of your implementation, that doesn't mean it will continue to work in future versions. Consider gcc and signed overflow behavior...
Since I based my -1 on a claim that we could get C code to generate the identical asm, I guess it's only fair to retract it when all the major compilers turn out to be junk in this regard..
|
5

The C23 standard provides a standard way to detect signed overflow caused by addition, subtraction and multiplication. The usage is quite straightforward. Instead of c = a + b; and worrying about undefined behaviors(UBs) when a + b overflows, you can now use bool overflowed = chk_add(&c, a, b);. If the result cannot be expressed in c, overflowed would be true, and you know that overflow has occurred and also get a wrapped value in c. Otherwise, c would contain the result of a + b. The standard library also provides the macros chk_sub and chk_mul for detecting potential overflows in subtraction and multiplication. These macros are defined in the header stdckdint.h.

The original proposal for this change can be found in N2683. Initially, only "plain" char, bool and enumeration types were excluded from the types. The proposal N2867 raised concerns about the lack of readily available implementations for bit-precise integer types, and those types were also excluded before the standard wording was finalized.

Here are the corresponding quotes from the latest C standard draft (N3220) at the time of writing:

Checked Integer Operation Type-generic Macros

Synopsis

bool ckd_add(type1 *result, type2 a, type3 b);
bool ckd_sub(type1 *result, type2 a, type3 b);
bool ckd_mul(type1 *result, type2 a, type3 b);

Description

These type-generic macros perform addition, subtraction, or multiplication of the mathematical values of a and b, storing the result of the operation in *result, (that is, *result is assigned the result of computing a + b, a - b, or a * b). Each operation is performed as if both operands were represented in a signed integer type with infinite range, and the result was then converted from this integer type to type1.

Both type2 and type3 shall be any integer type other than "plain" char, bool, a bit-precise integer type, or an enumerated type, and they may or may not be the same. *result shall be a modifiable lvalue of any integer type other than "plain" char, bool, a bit-precise integer type, or an enumerated type.

Recommended practice

It is recommended to produce a diagnostic message if type2 or type3 are not suitable integer types, or if *result is not a modifiable lvalue of a suitable integer type.

Returns

If these type-generic macros return false, the value assigned to *result correctly represents the mathematical result of the operation. Otherwise, these type-generic macros return true. In this case, the value assigned to *result is the mathematical result of the operation wrapped around to the width of *result.

EXAMPLE If a and b are values of type signed int, and result is a signed long, then ckd_sub(&result, a, b); indicates if a - b can be expressed as a signed long. If signed long has a greater width than signed int, this is the case and this macro invocation returns false.

More examples: Assuming CHAR_BIT==8 and sizeof(long long)>sizeof(int), the following assertions would not fire.

#include <stdbool.h>
#include <stdckdint.h>
#include <assert.h>
#include <limits.h>

int main(){
    signed char c; //Cannot use plain char here, as plain char can be signed or unsigned
    assert(!ckd_add(&c, 1, 1)); // No overflow for 1 + 1
    assert(c == 2);
    assert(ckd_add(&c, 127, 1));
    assert(c == -128); // 128 is wrapped into the range of signed char [-128, 127] and becomes -128
    assert(ckd_sub(&c, -100, 29)); 
    assert(c == 127); // Similarly, -129 is wrapped into [-128, 127] and becomes 127
    assert(!ckd_mul(&c, 0L, 10000)); // Types can be mixed. No overflow here                     
    assert(c == 0);
    assert(ckd_mul(&c, 32, 4));
    assert(c == -128); // 128 is wrapped to -128
    int ia = INT_MAX;
    long long l;
    // l = ia + 1; // UB, even though l is large enough to hold the result value;
    l = (long long)ia + 1; // OK
    assert(!ckd_add(&l, ia, 1)); // Also OK without explicit casting
    unsigned long long ul;
    ia = 1;                                                 
    ul = ia - 2; // Not UB, but the result may be surprising,                                                                    
                 // and unsigned overflow is by no means easier to detect than signed                               
    assert(ul == ULLONG_MAX);                               
    assert(ckd_sub(&ul, ia, 2)); //Can also be used to detect unsigned overflow                                     
    assert(ul == ULLONG_MAX);
}

For those who need to use it in earlier C versions or in C++, this GitHub repo provides a reference implementation of these macros.

Please note that if you need to implement your own version of these macros or implement a wrapper around them, your implementation should not use an identifier starting with ckd_ because those identifiers are potentially reserved since C23.

Comments

3

The obvious solution is to convert to unsigned, to get the well-defined unsigned overflow behavior:

int add(int lhs, int rhs) 
{ 
   int sum = (unsigned)lhs + (unsigned)rhs; 
   if ((lhs >= 0 && sum < rhs) || (lhs < 0 && sum > rhs)) { 
      /* an overflow has occurred */ 
      abort(); 
   } 
   return sum;  
} 

This replaces the undefined signed overflow behavior with the implementation-defined conversion of out-of-range values between signed and unsigned, so you need to check your compiler's documentation to know exactly what will happen, but it should at least be well defined, and should do the right thing on any twos-complement machine that doesn't raise signals on conversions, which is pretty much every machine and C compiler built in the last 20 years.

2 Comments

You're still storing the result in sum, which is an int. That results in either an implementation-defined result or an implementation-defined signal being raised if the value of (unsigned)lhs + (unsigned)rhs is greater than INT_MAX.
@R: that's the whole point -- the behavior is implementation defined, rather than undefined, so the implementation must document what it does, and do it consistently. A signal can only be raised if the implementation documents it, in which case it must always be raised and you can use that behavior.
2

Your fundamental problem is that lhs + rhs doesn't do the right thing. But if you're willing to assume a two's complement machine, we can fix that. Suppose you have a function to_int_modular that converts unsigned to int in a way that is guaranteed to be the inverse of conversion from int to unsigned, and it optimizes away to nothing at run time. (See below for how to implement it.)

If you use it to fix the undefined behavior in your original attempt, and also rewrite the conditional to avoid the redundant test of lhs >= 0 and lhs < 0, then you get

int add(int lhs, int rhs)
{
 int sum = to_int_modular((unsigned)lhs + rhs);
 if (lhs >= 0) {
  if (sum < rhs)
    abort();
 } else {
  if (sum > rhs)
   abort();
 }
 return sum; 
}

which should outperform the current top-voted answer, since it has a similar structure but requires fewer arithmetic operations.

(Reorganizing the if shouldn't be necessary, but in tests on godbolt, ICC and MSVC do eliminate the redundant test on their own, but GCC and Clang surprisingly don't.)

If you prefer to compute the result in a wider size and then bounds check, one way to do the bounds check is

 long long sum = (long long)lhs + rhs;
 if ((int)sum != sum)
  abort();

... except that the behavior is undefined on overflow. But you can fix that with the same helper function:

 if (to_int_modular(sum) != sum)

This will probably outperform the current accepted answer on compilers that aren't smart enough to optimize it to a test of the overflow flag.

Unfortunately, testing (visual inspection on godbolt) suggests that GCC, ICC and MSVC do better with the code above than with the code in the accepted answer, but Clang does better with the code in the accepted answer. As usual, nothing is easy.


This approach can only work on architectures where the ranges of int and unsigned are equally large, and the specific implementations below also depend on its being two's complement. Machines not meeting those specs are vanishingly rare, but I'll check for them anyway:

static_assert(INT_MIN + INT_MAX == -1 && UINT_MAX + INT_MIN == INT_MAX);

One way to implement to_int_modular is

inline int to_int_modular(unsigned u) {
    int i;
    memcpy(&i, &u, sizeof(i));
    return i;
}

All major x64 compilers have no trouble optimizing that to nothing, but when optimizations are disabled, MSVC and ICC generate a call to memcpy, which may be a bit slow if you use this function a lot. This implementation also depends on details of the representation of unsigned and int that probably aren't guaranteed by the standard.

Another way is this:

inline int to_int_modular(unsigned u) {
    return u <= INT_MAX ? (int)u : (int)(u - INT_MIN) + INT_MIN;
}

All major x64 compilers optimize that to nothing except ICC, which makes an utter mess of it and every variation that I could think of. ICX does fine, and it appears that Intel is abandoning ICC and moving to ICX, so maybe this problem will fix itself.

1 Comment

You can add that with C2X making signed integer overflow defined (as all produced architectures now work on 2s complement), this can be simplified to the approach of Hacker's Delight: var sum: ST = a +% b; (+% being wrapping addition). if (((sum ^ a) & (sum ^ b)) < 0) overflow(); return sum;
1

You may have better luck converting to 64-bit integers and testing similar conditions like that. For example:

#include <stdint.h>

...

int64_t sum = (int64_t)lhs + (int64_t)rhs;
if (sum < INT_MIN || sum > INT_MAX) {
    // Overflow occurred!
}
else {
    return sum;
}

You may want to take a closer look at how sign extension will work here, but I think it is correct.

1 Comment

Note that this won't work in ILP64 environments where ints are 64-bit.
1

How about:

int sum(int n1, int n2)
{
  int result;
  if (n1 >= 0)
  {
    result = (n1 - INT_MAX)+n2; /* Can't overflow */
    if (result > 0) return INT_MAX; else return (result + INT_MAX);
  }
  else
  {
    result = (n1 - INT_MIN)+n2; /* Can't overflow */
    if (0 > result) return INT_MIN; else return (result + INT_MIN);
  }
}

I think that should work for any legitimate INT_MIN and INT_MAX (symmetrical or not); the function as shown clips, but it should be obvious how to get other behaviors).

3 Comments

+1 for nice alternate approach that's perhaps more intuitive.
I think this - result = (n1 - INT_MAX)+n2; - could overflow, if n1 was small (say 0) and n2 was negative.
@davmac: Hmm... maybe it's necessary to break out three cases: start with one for (n1 ^ n2) < 0, which on a two's-complement machine would imply that the values have opposite sign and may be added directly. If the values have the same sign, then the approach given above would be safe. On the other hand, I'm curious if the authors of the Standard expected that implementations for two's-complement silent-overflow hardware would jump the rails in case of overflow in a way that didn't force an immediate Abnormal Program Termination, but caused unpredictable disruption of other computations.
0

In case of adding two long values, portable code can split the long value into low and high int parts (or into short parts in case long has the same size as int):

static_assert(sizeof(long) == 2*sizeof(int), "");
long a, b;
int ai[2] = {int(a), int(a >> (8*sizeof(int)))};
int bi[2] = {int(b), int(b >> (8*sizeof(int))});
... use the 'long' type to add the elements of 'ai' and 'bi'

Using inline assembly is the fastest way if targeting a particular CPU:

long a, b;
bool overflow;
#ifdef __amd64__
    asm (
        "addq %2, %0; seto %1"
        : "+r" (a), "=ro" (overflow)
        : "ro" (b)
    );
#else
    #error "unsupported CPU"
#endif
if(overflow) ...
// The result is stored in variable 'a'

1 Comment

Portable code would use CHAR_BIT rather than 8.... ;)
-1

By me, the simplest check would be checking the signs of the operands and of the results.

Let's examine sum: the overflow could occur in both directions, + or -, only when both operands have the same sign. And, obviously, the overflow will be when the sign of the result won't be the same as the sign of the operands.

So, a check like this will be enough:

int a, b, sum;
sum = a + b;
if  (((a ^ ~b) & (a ^ sum)) & 0x80000000)
    detect_oveflow();

Edit: as Nils suggested, this is the correct if condition:

((((unsigned int)a ^ ~(unsigned int)b) & ((unsigned int)a ^ (unsigned int)sum)) & 0x80000000)

And since when the instruction

add eax, ebx 

leads to undefined behavior? There is no such thing in the Intel x86 instruction set reference.

7 Comments

You're missing the point here. Your second line of code sum = a + b could yield undefined behavior.
if you cast sum, a and b to unsigned during your test-addition your code will work btw..
It's undefined not because the program will crash or will behave differently. It's the exact thing the processor is doing to compute the OF flag. The standard is just trying to protect itself from non-standard cases, but it doesn't mean that you are not allowed to do this.
@Nils yeah, i wanted to do that, but I thought that four (usngined int)s will make it much more unreadable. (you know, you first read it, and try it only if you liked it).
the undefined behavior is in C, not after compiling to assembly
|
-4

I think that this works:

int add(int lhs, int rhs) {
   volatile int sum = lhs + rhs;
   if (lhs != (sum - rhs) ) {
       /* overflow */
       //errno = ERANGE;
       abort();
   }
   return sum;
}

Using volatile keeps the compiler from optimizing away the test because it thinks that sum may have changed between the addition and the subtraction.

Using gcc 4.4.3 for x86_64 the assembly for this code does do the addition, the subtraction, and the test, though it stores everything on the stack and of unneeded stack operations. I even tried register volatile int sum = but the assembly was the same.

For a version with only int sum = (no volatile or register) the function did not do the test and did the addition using only one lea instruction (lea is Load Effective Address and is often used to do addition without touching the flags register).

Your version is larger code and has a lot more jumps, but I don't know which would be better.

6 Comments

-1 for misuse of volatile to mask undefined behavior. If it "works", you're still just "getting lucky".
@R: If it doesn't work the compiler isn't implementing volatile correctly. All I was trying for was a simpler solution to a very common problem on an already answered question.
Where it might fail, though, would be a system whose numeric representation wrap to lower values upon overflow for integers.
@nategoose, your assertion that "if it doesn't work the compiler isn't implementing volatile correctly" is wrong. For one thing, in two's complement arithmetic, it will always be true that lhs = sum - rhs even if overflow occurred. Even if that were not the case, and although this particular example is a bit contrived, the compiler might for instance generate code which performs the addition, stores the result value, reads the value back into another register, compares the stored value with the read value and notices that they are the same and therefore assume no overflow has occurred.
(you're also assuming that causing the overflow won't cause the comparison afterwards to go wrong or even be skipped, which "undefined behavior" allows).
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.