51

I recently read that signed integer overflow in C and C++ causes undefined behavior:

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.

I am currently trying to understand the reason of the undefined behavior here. I thought undefined behavior occurs here because the integer starts manipulating the memory around itself when it gets too big to fit the underlying type.

So I decided to write a little test program in Visual Studio 2015 to test that theory with the following code:

#include <stdio.h>
#include <limits.h>

struct TestStruct
{
    char pad1[50];
    int testVal;
    char pad2[50];
};

int main()
{
    TestStruct test;
    memset(&test, 0, sizeof(test));

    for (test.testVal = 0; ; test.testVal++)
    {
        if (test.testVal == INT_MAX)
            printf("Overflowing\r\n");
    }

    return 0;
}

I used a structure here to prevent any protective matters of Visual Studio in debugging mode like the temporary padding of stack variables and so on. The endless loop should cause several overflows of test.testVal, and it does indeed, though without any consequences other than the overflow itself.

I took a look at the memory dump while running the overflow tests with the following result (test.testVal had a memory address of 0x001CFAFC):

0x001CFAE5  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x001CFAFC  94 53 ca d8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Overflowing integer with memory dump

As you see, the memory around the int that is continuously overflowing remained "undamaged". I tested this several times with similar output. Never was any memory around the overflowing int damaged.

What happens here? Why is there no damage done to the memory around the variable test.testVal? How can this cause undefined behavior?

I am trying to understand my mistake and why there is no memory corruption done during an integer overflow.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Vinz
  • 3,030
  • 4
  • 31
  • 52
  • 37
    You expect to get a definition of the behaviour which is "undefined"?! You are explicitly told that there are no reasonable expectations that you can hold, so the behaviour cannot possibly differ from anything you're allowed to expect. – Kerrek SB May 19 '16 at 14:01
  • 8
    Integer overflow doesn't affect adjacent memory. – sashoalm May 19 '16 at 14:01
  • In what other way should it become undefined? The value does not have the defined size, yes that's true but doesn't UB mean the memory gets corrupted and that causes undefined things to happen? I mean an int going something like -2147483648 after overflowing is kind of defined, isn't it? – Vinz May 19 '16 at 14:03
  • 9
    @NathanOliver, there is no harm in reasoning undefined behavior. I personally find it very useful excercise. – SergeyA May 19 '16 at 14:04
  • 2
    Trying to define _undefined behaviour_. Look up the word in a dictionary. And **don't post images of text!** – too honest for this site May 19 '16 at 14:05
  • 7
    @Olaf UB has a reason, and I'm trying to make that out. The image does not contain a crucial part of the question but is rather there for the graphical illustration of my test results. Everything in the image, also the used code, has been posted as clear text. – Vinz May 19 '16 at 14:08
  • 21
    Downvoting this question is utterly wrong in my opinion. OP actually shows a very healthy desire to understand, rather than blindly follow. – SergeyA May 19 '16 at 14:13
  • @SergeyA: He might, but did not show any research effort. E.g.: not inform if C has arbitrary width integers. A simple search would have answered that question. Similar for not checking the assembler code. how the increment is performed on **his** platform. Not to mention postion images of text. – too honest for this site May 19 '16 at 14:16
  • 3
    @Olaf, question might have been asked better (I am not fan of this image either). But I still believe that there is more quality in it than downvotes suggest. – SergeyA May 19 '16 at 14:18
  • 3
    If you rephrase this as a hardware (x86) question instead of a C++ question, it would be much better. On x86 adding 1 to 0x7fffffff (max positive value) gets 0x80000000 (smallest negative value) . But other machines will give different results. – brian beuning May 19 '16 at 14:23
  • I am sorry for all the misunderstandings around my post. I edited it and I hope it's now clear that I tried to understand why my theory, that the integer overflow causes memory corruption, is wrong :) Thank you all for your answers and comments! – Vinz May 19 '16 at 14:24
  • @brianbeuning thank you, I added an x86 tag to the question. You're completely right, it's architecture based – Vinz May 19 '16 at 14:28
  • 1
    @brianbeuning: That is not guaranteed. A compiler can (and some will sometimes) detect overflow operations and generate code which does not behave as expected - absolutely compliant to the standard. – too honest for this site May 19 '16 at 14:36
  • Unfortunately, it's not just your nose that the demons can come out of:( – Martin James May 19 '16 at 14:43
  • 4
    @Olaf The question appears to be to be based on not _quite_ understanding the meaning of "undefined behavior" in this context. I doubt any amount of googling is going to correct it, since my experience is that tutorials generally assume the reader can infer the correct meaning. I think this is a very good question. – Izkata May 19 '16 at 21:28
  • 2
    @Izkata: Well, at the risk of sounding harsh: This is basic laguage competence. If this is a translation problem (which I doubt from the text), it can be easily translated. Otherwise (here come the (potentially harsh) truth: We very well can expect some research from the OP. And expecting defined behaviour from something undefined by its explicit nature is an oxymoron par exellence. – too honest for this site May 19 '16 at 21:39
  • 2
    @Olaf By "context", I don't mean human language. I mean a technical context vs a non-technical context. "Undefined behavior" can mean anything in English, but in programming it does refer to a subset of what the phrase means in plain English. It sounds to me like OP hasn't quite picked up on that, and as I said, the difference is rarely explained anywhere. – Izkata May 19 '16 at 21:46
  • 1
    @Izkata: 1) This is a site about programming questions, so the semantic set is obvious. 2) You are very welcome to provide an example in any context where "**un**defined" means "somewhat defined". 3) If that does not become clear after some minor research, maybe programming (or anything involving engineering) is not the target one should strive to (I met a lot of such people and they might be good at other things I'm very bad at). 4) If you fall from a high building there is little use in bargaining with gravity. – too honest for this site May 19 '16 at 22:01
  • 2
    @brianbeuning: The Standard made integer overflow Undefined Behavior for the purpose of allowing efficient C implementations on hardware where it could have nasty possibly-unpredictable effects, but hyper-modern compiler philosophy dictates that because the Standard's lack of requirements with regard to overflow is more important than the predictability of the hardware response. – supercat May 19 '16 at 22:07
  • 1
    @Olaf: The Standard was written on the premise that if some implementations can cheaply define a behavior in some situation and others can't, the Standard should leave the task of defining behavior in those situations to the implementations that can support them. In many cases, implementations would be designed in a way that would naturally offer some behavioral guarantees, but since the Standard lacks the terminology to describe non-deterministic behaviors many implementations didn't bother to document them at all. That's unfortunate because there are many situations where... – supercat May 19 '16 at 22:22
  • ...a function will, as part of its duties, compute a value that may or may not be meaningful, for code that may or may not care about it. If integer calculations can be guaranteed to have no side-effects (a cheap guarantee on most platforms), performing such calculations unconditionally in a way that would give arbitrary results in case of overflow may be cheaper than having to determine whether the results are required or safe. Since few compilers writers bothered to document "This calculation may give arbitrary results but have no side-effects", even though the design of their compilers... – supercat May 19 '16 at 22:25
  • ...would have naturally guaranteed it, and since on many platforms there's really no reason why integer calculations ever should have side-effects (any counter-examples for general-purpose microcomputer compilers prior to 2000?), the normal interpretation of "Undefined Behavior" meant "Yields a result that might not behave consistently as a value of the indicated type, but otherwise has no side-effects". – supercat May 19 '16 at 22:28
  • @supercat: You might be surprised how far a good compiler might optimize code if it detects UB. gcc is quite famous exploiting such weaknesses in the code. And I support it ver well. Things might (and do) differ on special platforms where a specific behaviour is wishful (e.g. saturation), possibly with pargmas or intrinsics. This still would allow to use a higher abstraction level than Assembler for the code, while still providing fine-grain control. That is one reason C has become so very popular. – too honest for this site May 20 '16 at 03:09
  • @sashoalm **"Integer overflow doesn't affect adjacent memory"** : On my platform, it does. (Yes, I'm kidding. But it may affect whatever it wants. That's the whole issue with UB). – Marco13 May 20 '16 at 08:40
  • 2
    @supercat I guess Olaf is hinting at things like http://stackoverflow.com/q/7682477 or the famous [gcc bug 30475](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=30475). Undefined is undefined. The developer may think: "Hey, I'm on a 32bit x86 platform with 2-complement numbers, I **know** what happens on overflow", but due to UB even seemingly reasonable code may produce complete garbage. – Marco13 May 20 '16 at 08:44
  • @Olaf `You are very welcome to provide an example in any context where "undefined" means "somewhat defined".` OP's example is one. It's doing the same thing on his system every run, so it's consistent. English "undefined behavior" would expect different results every run. – Izkata May 20 '16 at 14:03
  • @Olaf: I am not surprised at how far hyper-modern compilers will go to exploit the Standard's decision to ignore behaviors that were 100% consistent on compilers for microcomputers. And I disagree with your assessment as to why C became popular. C became popular because in cases where a program's edge-case requirements were loosely defined and could be satisfied by natural platform behavior without special-case code, there was often no need for the programmer or compiler to generate boundary checks. If a function was supposed to, among its other duties, ... – supercat May 20 '16 at 14:13
  • 2
    @Izkata: The term "undefined" does not require **random** behaviour. Worse, it includes very well getting the same (and even expected) results. The worst thing happening with UB is that it passes unnoticed (which does not imply without doing harm). – too honest for this site May 20 '16 at 14:17
  • ...perform a computation which may or may not be meaningful, for code that may or may not use the result, being able to ignore overflow in cases where the result would end up getting discarded anyway allowed code to be more efficient. I would posit that a good optimizer should never make it necessary to generate significantly slower code than could be generated by a programmer with a more simplistic compiler. While I'm not surprised at how hyper-modern compilers behave, I disagree with your use of the term "good". – supercat May 20 '16 at 14:18
  • @supercat: That is exactly what I say. It leaves such checks to the programmer and concentrates on best performance. Much like Assembler does. OTOH you can use datastructures (which is the actual pro of C vs. Assembler) for higher abstraction levels. – too honest for this site May 20 '16 at 14:19
  • 2
    @Olaf Like I said in my first comment, "undefined behavior" has different nuance in a technical context (which is what you're arguing and _I'm not disagreeing_), and in plain English. This difference is rarely explicitly explained to new programmers. You just seem to have been coding for so long that the technical meaning is the default in your head. – Izkata May 20 '16 at 14:25
  • @Olaf: C compilers allowed programmers the freedom to decide whether edge-case code should be included. Hyper-modern compilers force programmers to include edge-case code, which in many cases compilers can't optimize out, *even in cases where machine code with the edge-case-handling code omitted would have worked just fine*. – supercat May 20 '16 at 14:39
  • 1
    @supercat: I strongly disagree. You always had to ensure not to cause undefined behaviour. For instance signed overflow: Wrapping 2s complement is always a problem, so you have to ensure your code is safe anyway. Ranting about "hypermodern" (whatever that means) compilers is useless. If you want to exploit machine-level behaviour, go to the machine-level; that is the reason inline-Assembler exists. Otherwise write correct code, it is not that complicated. – too honest for this site May 20 '16 at 14:44
  • @Izkata: The meaning, even in technical contexts, has changed. At the time the C Standard was written, the authors noted that the majority of implementations had defined overflow behavior such that the behavior of `uint1 = int1 * (signed)uchar1;` and `uint1 = int1 * (unsigned)uchar1` would be equivalent for results between `INT_MAX+1u` and `UINT_MAX` unless the result was used in specific ways. While the Standard didn't require implementations to behave that way, the authors of the rationale noted that most current implementations did, and they likely expected that to remain true forever. – supercat May 20 '16 at 14:45
  • @Olaf: Suppose one needs to compute `long l2=i1*i2+l1;` on a system with a fast multiplier in cases where i1 and i2 are both in the range -1000 to 1000, or set l2 to any value otherwise. What's should be the most efficient way to accomplish that? Writing it `long l2=(int)((unsigned)i1*i2)+l1;` would on many platforms generate less efficient code than the original, but the original machine code would meet requirements in all cases where the compiler doesn't try to draw inappropriate inferences about i1 and i2. – supercat May 20 '16 at 14:51
  • That would invoke UB on I16 platforms already. And why the cast to `unsigned`? As you wrote the range includes negative values. Just `(long)i1*i2)+l1` is sufficient. A good compiler can well use e.g. 32*32->32 multiplication for I32L64 platforms or 16*16->32 on I16, or whatever is optimal. Not sure what your problem is here. – too honest for this site May 20 '16 at 14:57
  • @Olaf: For I16 platforms, use -100 to +100 as the value range, and for 32-bit platforms, use "long long". Some platforms' multiply instructions can compute a double-length result faster than a sign-extended single-length result, but for others (e.g. Cortex-M0) that's not the case. A 32x32->64 multiply will be more than four times as slow as 32x32->32 on that platform. If code doesn't care about what value is computed when i1 and i2 are outside the given range, why spend extra time on computations for that case? – supercat May 20 '16 at 15:01
  • @Olaf: Also, have you read the rationale for C89's decision to make short unsigned types promote to signed int? The Standard doesn't require implementations to define overflow behavior, but the authors of the Standard regarded the majority of then-current implementations as doing so. – supercat May 20 '16 at 15:02
  • @supercat: No, and I don't care about C89. Only C standard is C11 and that leaves the conversion to the implementation. Which means the implementation has to document it. Anyway, you don't discuss, but just throw your statements at me. I'm out. – too honest for this site May 20 '16 at 15:05
  • 2
    @Olaf You aren't helping anyone by berating them for not understanding a concept and accusing them of not performing cursory research. Do you think that anyone would honestly think that a google search is harder work than writing a pretty long stackoverflow question like this one? It's obvious that OP has a misconception of the standard's concept of undefined behaviour and why it uses such a concept. A dictionary will not clarify their misunderstanding and you know that. – Jordan Melo May 25 '16 at 19:26

6 Answers6

78

You misunderstand the reason for undefined behavior. The reason is not memory corruption around the integer - it will always occupy the same size which integers occupy - but the underlying arithmetics.

Since signed integers are not required to be encoded in 2's complement, there can not be specific guidance on what is going to happen when they overflow. Different encoding or CPU behavior can cause different outcomes of overflow, including, for example, program kills due to traps.

And as with all undefined behavior, even if your hardware uses 2's complement for its arithmetic and has defined rules for overflow, compilers are not bound by them. For example, for a long time GCC optimized away any checks which would only come true in a 2's-complement environment. For instance, if (x > x + 1) f() is going to be removed from optimized code, as signed overflow is undefined behavior, meaning it never happens (from compiler's view, programs never contain code producing undefined behavior), meaning x can never be greater than x + 1.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • 1
    So... Can you give a single example of a MODERN architecture that DOES NOT use twos complement? – Jon Trauntvein May 19 '16 at 14:09
  • Vinzenz, I made a solution to your program and it doesn't segfault anymore, but it overflows as you wanted. – Mirakurun May 19 '16 at 14:10
  • @JonTrauntvein, I am not aware of any :). – SergeyA May 19 '16 at 14:12
  • 5
    @SergeyA Exactly! I was trying to understand the reason for the UB and guessed it would be because of memory corruption happening during the overflow. Now I know it has arithmetic backgrounds :) Thank you again, and I don't think the downvotes harm much... I wont delete this question as it might be helpful for someone else thinking just as I did :) – Vinz May 19 '16 at 14:16
  • @JonTrauntvein: I would be genuinely shocked if any system built after 1975 used anything other than two's complement to represent signed integers; however, it's not guaranteed to be the case. It's entirely possible for there to be some oddball architecture using one's complement for some bizarre reason. – John Bode May 19 '16 at 14:30
  • 2
    @JonTrauntvein: C++ is designed for more than modern architectures. – Martin York May 19 '16 at 15:35
  • 15
    @JonTrauntvein Some DSP support latching arithmetic. Adding 1 to the largest value stays the largest value. That way an overflow bug does not cause your missile to go 180 of the desired direction. – brian beuning May 19 '16 at 15:41
  • 2
    @Vinzenz: Note that a specific implementation of C (like MSVC) *could* define what happens when a signed integer overflows (i.e. guarantee correct behaviour with 2's complement integers, because that's what the underlying hardware supports.) Writing code that depends on this would not be safe even for x86: Some compilers (like gcc and clang) [tag advantage of UB to optimize more](http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html). e.g. in a loop with an `int` loop counter indexing an array, the compiler can skip the sign-extension from 32b to 64b on each iteration. – Peter Cordes May 19 '16 at 15:54
  • 1
    This answer would be better if you mention that you can't assume that C signed integers behave like asm on the target hardware, see my previous comment. gcc and clang choose to compile `while (x+1 > x) x++;` into an infinite loop for signed x, unless you use `-fwrapv` to make signed overflow fully defined (as 2's complement wrapping). Interestingly, the gcc man page really does say "2's complement", perhaps implying that all the targets supported by gcc use 2's complement. – Peter Cordes May 19 '16 at 16:00
  • @PeterCordes, yes, this a good addition (however, it somewhat generic to all undefined behavior). I will make edit. – SergeyA May 19 '16 at 16:08
  • 4
    Yes, it's true for multiple kinds of UB. The problem is that your answer kind of implies that there are limits to the consequences of the UB. It seems to imply that arithmetic on C signed integers will be 2's complement on 2's complement hardware, which is *not true* for compilers that optimize aggressively like gcc and clang. I think this is a really important point, otherwise people will be tempted to rely on signed overflow since they know they're targeting 2's complement hardware. Thanks for the update. – Peter Cordes May 19 '16 at 16:13
  • @JonTrauntvein http://stackoverflow.com/questions/6971886/exotic-architectures-the-standards-committees-care-about – T.C. May 19 '16 at 17:46
  • 1
    "from compiler's view, programs never contain code producing undefined behavior" -- specifically, from the compiler's point of view it doesn't matter what happens to programs that contain undefined behaviour. Therefore any reasoning of the form "if the program contains no UB then this is a good idea" is sufficient justification to go ahead. At least, so far as standard-compliance is concerned. – Steve Jessop May 19 '16 at 22:04
  • There are some platforms where near-simultaneous receipt of different kinds of interrupts can cause program execution to jump to some basically random address. It is not implausible that some machines may have been built with overflow-trapping hardware which can malfunction, corrupting memory, if an interrupt arrives at just the wrong time; making overflow UB allows for such possibilities, among others. – supercat May 19 '16 at 22:46
29

The authors of the Standard left integer overflow undefined because some hardware platforms might trap in ways whose consequences could be unpredictable (possibly including random code execution and consequent memory corruption). Although two's-complement hardware with predictable silent-wraparound overflow handling was pretty much established as a standard by the time the C89 Standard was published (of the many reprogrammable-microcomputer architectures I've examined, zero use anything else) the authors of the Standard didn't want to prevent anyone from producing C implementations on older machines.

On implementations which implemented commonplace two's-complement silent-wraparound semantics, code like

int test(int x)
{
  int temp = (x==INT_MAX);
  if (x+1 <= 23) temp+=2;
  return temp;
}

would, 100% reliably, return 3 when passed a value of INT_MAX, since adding 1 to INT_MAX would yield INT_MIN, which is of course less than 23.

In the 1990s, compilers used the fact that integer overflow was undefined behavior, rather than being defined as two's-complement wrapping, to enable various optimizations which meant that the exact results of computations that overflowed would not be predictable, but aspects of behavior that didn't depend upon the exact results would stay on the rails. A 1990s compiler given the above code might likely treat it as though adding 1 to INT_MAX yielded a value numerically one larger than INT_MAX, thus causing the function to return 1 rather than 3, or it might behave like the older compilers, yielding 3. Note that in the above code, such treatment could save an instruction on many platforms, since (x+1 <= 23) would be equivalent to (x <= 22). A compiler may not be consistent in its choice of 1 or 3, but the generated code would not do anything other than yield one of those values.

Since then, however, it has become more fashionable for compilers to use the Standard's failure to impose any requirements on program behavior in case of integer overflow (a failure motivated by the existence of hardware where the consequences might be genuinely unpredictable) to justify having compilers launch code completely off the rails in case of overflow. A modern compiler could notice that the program will invoke Undefined Behavior if x==INT_MAX, and thus conclude that the function will never be passed that value. If the function is never passed that value, the comparison with INT_MAX can be omitted. If the above function were called from another translation unit with x==INT_MAX, it might thus return 0 or 2; if called from within the same translation unit, the effect might be even more bizarre since a compiler would extend its inferences about x back to the caller.

With regard to whether overflow would cause memory corruption, on some old hardware it might have. On older compilers running on modern hardware, it won't. On hyper-modern compilers, overflow negates the fabric of time and causality, so all bets are off. The overflow in the evaluation of x+1 could effectively corrupt the value of x that had been seen by the earlier comparison against INT_MAX, making it behave as though the value of x in memory had been corrupted. Further, such compiler behavior will often remove conditional logic that would have prevented other kinds of memory corruption, thus allowing arbitrary memory corruption to occur.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • 2
    One reason for the off-the-rails, that users don't always appreciate while they're swearing at their compiler, is that the compiler isn't written with the assumption that you'd intentionally write code with UB expecting the compiler will do something sensible. Rather, it's written on the assumption that if it sees the above code then it's probably as a result of some kind of edge-case, like maybe `INT_MAX` is the result of a macro, and so it *should* optimize it as a special case. If you ever change `INT_MAX` in that code back to something that isn't silly, it'll stop optimizing. – Steve Jessop May 19 '16 at 22:12
  • @SteveJessop: Many programs could tolerate almost any form of overflow behavior provided two constraints are met: (1) Integer math, other than attempted division by zero, has no side-effects; (2) Converting the N-bit result of signed additive, multiplicative, or bitwise operations to an N-bit-or-smaller unsigned type will yield the same result as if the operation had been performed using unsigned math. The authors of the C89 noted that most compilers upheld both guarantees, and the choice of signed promotion for short unsigned types was based in part upon that behavior. – supercat May 19 '16 at 22:38
  • @SteveJessop: If there were a way to assert those two requirements, a program which took advantage of them, fed through a compiler that upheld them, could run faster than could any remotely-readable strictly-conforming program run through the most perfect compiler imaginable. Standard C lacks any means of keeping programs on the rails while still granting compilers some freedom with regard to overflow behavior, so even the best compiler will be stuck having to abide by the overly-restrictive requirements posed by strictly-conforming programs. – supercat May 19 '16 at 22:42
  • Well quite: if C offered guarantees that are more intuitive to programmers, then programmers wouldn't be tempted in the first place to write code with undefined behaviour. Good luck with the next portable assembler language, this one's encumbered in technical debt. The solution in practice is *usually* to write non-strictly-conforming C aimed at what you think your compiler probably guarantees, and if it breaks anyway then ask the compiler vendor to help you figure out which optimizations to disable whilst loudly accusing the compiler of being "buggy" ;-) – Steve Jessop May 19 '16 at 22:50
  • @SteveJessop: Does totally-off-the-rails behavior enable useful optimizations that outweigh the inability to efficiently handle situations where a function, among its other duties, performs a cheap calculation whose result will sometimes be meaningful and sometimes not, for code which will sometimes use that result and sometimes ignore it? If overflows never occur in cases where results are used, is any useful purpose served by requiring code to prevent overflows in cases where the results would be ignored anyway? – supercat May 19 '16 at 22:52
  • 5
    @SteveJessop: A fundamental problem I think is that some people have gotten the crazy idea that the C Standard was intended to describe everything important about quality implementations. If one recognizes that (1) in a good implementation the abstract machine will generally inherit features and guarantees from the real execution platform upon which it is running; (2) different kinds of program can tolerate different levels of divergence between the real and abstract platforms; (3) there would be huge value in having a defined category of "selectively-conforming" programs which... – supercat May 19 '16 at 22:59
  • 5
    @SteveJessop: ...would not need to compile on every platform, but would be required to run correctly on every compliant platform where they compile (conversely, a compliant platform would not be required to run a significant fraction of selectively-conforming programs, but would be required to reject any selectively-conforming programs whose requirements it couldn't meet). As it is now, "conformance" is defined so loosely as to be essentially meaningless, and "strict conformance" is defined so strictly that few real-world tasks can be accomplished with strictly-conformant code. – supercat May 19 '16 at 23:03
  • As already mentioned in a question comment: Paraphrasing your statement about the code snippet that you posted, the code in [this question](http://stackoverflow.com/q/7682477) would **"100% reliably"** **not** cause an infinite loop. But it does. It's undefined. – Marco13 May 20 '16 at 08:47
  • @Marco13: That other program uses what I would call "loose" integer semantics, wherein the `i+=i;` would be allowed to behave as though `i` could non-deterministically hold either the wrapped value or the arithmetically-correct sum. Such semantics don't bother me because they allow useful (sometimes 2x speed) optimizations in cases where code wouldn't care about what value is produced in case of overflow. My gripe is with hyper-modern compilers which force programmers to avoid overflow *even in cases when they would not otherwise care about the result*. – supercat May 20 '16 at 14:08
  • @Marco13: What I find objectionable is the idea that the compilers for two's-complement machines should break things like `unsigned mult(int i, unsigned char c) { return i*c;}`, especially given that the decision to make unsigned characters promote to signed int was motivated by the fact that the majority of then-current compilers wouldn't need to have the character promote as unsigned to ensure arithmetically-correct behavior for all values up to UINT_MAX. Would the C89 authors have said that if programmers weren't allowed to use such behavior on two's-complement platforms? – supercat May 20 '16 at 14:57
  • This could end in an extensive discussion (and it did so elsewhere). I'm not an expert with the details, but was perplexed by some odd corner cases of UB-justified ""optimizations"", and by the fact that *technically*, 1. adding two values and 2. writing to invalid memory locations are "on the same level of undefinedness". I think that for 1., saying that "the result of the addition is unspecified" would leave enough freedom for the compiler guys. UB, in all its broadness, is odd for something as trivial as an addition... – Marco13 May 20 '16 at 19:27
  • @Marco13: There are hardware platforms that trap integer overflow in ways which are outside the compiler's control but could possibly be useful [triggering warning buzzers, etc.], and on such platforms it may not be practical to define integer overflow behavior as anything other than UB. The problem is that the Standard doesn't have a category of behavior for things that implementations should specify *whenever practical*. – supercat May 20 '16 at 19:39
  • @Marco13: I should mention another difficulty which is that many things that invoke UB often have real-world consequences that are bounded in ways the Standard cannot describe. For example, the behavior of `uint16_t foo(uint32_t z) {}` is defined if the caller ignores the result. If the caller does `uint16_t x=foo(0x12345678);` a probable consequence on many real implementations will be `x` holding 0x12345678. If code can deal with that, execution will stay on the rails, but there's no way in the Standard can allow a uint16_t to hold 0x12345678 except by declaring UB. – supercat May 20 '16 at 19:52
  • Even in the 1980's, the Motorola 68000 processor set the condition code registers for add and sub operations according to the true mathematical result. So if you wrote if (x + y >= 0) you could use a sequence ADD, Branch if Greater or Equal, which would branch if the mathematical result was >= 0, for example if x == y == INT_MAX. To get the behaviour that was typical in other compilers (x + y is negative), you would have to insert a compare instruction checking if the result is >= 0. So the fact that overflow is undefined behaviour allowed faster (and arguably more correct) code. – gnasher729 Jul 30 '17 at 00:29
  • @gnasher729: Allowing an implementation to regard expressions like x+y as yielding either the truncated result or an arithmetically-correct one, chosen in Unspecified fashion, would allow most of the useful optimizations that would be enabled by allowing totally arbitrary behavior, but would allow programmers to write more efficient code than would be possible if they had to avoid overflow at all costs. – supercat Jul 30 '17 at 01:54
5

Undefined behaviour is undefined. It may crash your program. It may do nothing at all. It may do exactly what you expected. It may summon nasal demons. It may delete all your files. The compiler is free to emit whatever code it pleases (or none at all) when it encounters undefined behaviour.

Any instance of undefined behaviour causes the entire program to be undefined - not just the operation that is undefined, so the compiler may do whatever it wants to any part of your program. Including time travel: Undefined behavior can result in time travel (among other things, but time travel is the funkiest).

There are many answers and blog posts about undefined behaviour, but the following are my favorites. I suggest reading them if you want to learn more about the topic.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
  • 3
    nice copy paste... While I fully understand the definition of "undefined" I was trying to understand the reason of the UB which is rather good defined as you can see by @SergeyA s answer – Vinz May 19 '16 at 14:26
  • 2
    Can you find any evidence of overflow on two's-complement silent-wraparound hardware having side effects beyond returning a meaningless result prior to 2005 or so? I despise the claim that it was never reasonable for programmers to expect microcomputer compilers to uphold behavioral conventions which were not consistently supported on mainframes or minicomputers but as far as I can tell had been absolutely unanimously supported by microcomputer compilers. – supercat May 19 '16 at 17:34
5

In addition to the esoteric optimization consequences, you've got to consider other issues even with the code you naively expect a non-optimizing compiler to generate.

  • Even if you know the architecture to be twos complement (or whatever), an overflowed operation might not set flags as expected, so a statement like if(a + b < 0) might take the wrong branch: given two large positive numbers, so when added together it overflows and the result, so the twos-complement purists claim, is negative, but the addition instruction may not actually set the negative flag)

  • A multi-step operation may have taken place in a wider register than sizeof(int), without being truncated at each step, and so an expression like (x << 5) >> 5 may not cut off the left five bits as you assume they would.

  • Multiply and divide operations may use a secondary register for extra bits in the product and dividend. If multiply "can't" overflow, the compiler is free to assume that the secondary register is zero (or -1 for negative products) and not reset it before dividing. So an expression like x * y / z may use a wider intermediate product than expected.

Some of these sound like extra accuracy, but it's extra accuracy that isn't expected, can't be predicted nor relied upon, and violates your mental model of "each operation accepts N-bit twos-complement operands and returns the least significant N bits of the result for the next operation"

Random832
  • 37,415
  • 3
  • 44
  • 63
  • If compiling for a target where `add` doesn't set the sign flag accurately based on the result, a compiler would know that and use a separate test/compare instruction to produce correct results (assuming `gcc -fwrapv` so signed overflow has defined wrapping semantics). C compilers don't just make asm that looks like the source; they take care to make code that has exactly the same semantics as the source, unless UB lets them optimize (e.g. not redoing sign-extension of the loop counter every iteration indexing). – Peter Cordes May 20 '16 at 04:30
  • In summary, the only way any of the things you described could happen (other than compiler bugs) is from the "esoteric optimizations" that assume signed overflow won't happen, and expressions involving signed integer thus imply bounds on the possible range of values. Everything you describe is an "esoteric optimization consequence", and won't happen with `gcc -fwrapv`, or similar options for other compilers. – Peter Cordes May 20 '16 at 04:33
  • 1
    @Peter Cordes - None of these things are esoteric, they're entirely natural consequences of writing the natural assembly code that corresponds to the meaning of the equivalent C code. `-fwrapv` is itself an esoteric option, and the things that it does are not mere "disabled optimizations". The source doesn't actually have the semantics you're asserting it has. – Random832 May 20 '16 at 04:45
  • So you're talking about `gcc -O0` (i.e. `-fno-strict-overflow`, but not `-fwrapv`)? Are you sure about these? I mean, `f((unsigned)a + (unsigned)b < (unsigned)INT_MAX)` must be compiled correctly, with a separate compare if the add doesn't set the sign flag in a useful way. I don't think it's plausible for the compiler to get the signed version of the same branch wrong other than by optimizing it away. – Peter Cordes May 20 '16 at 04:55
  • Unsigned comparison doesn't use the same flags as signed comparison. There is an overflow flag, and it is used for signed comparisons, but it's designed to give fully correct results for subtraction (`a < b` === `a - b < 0` even if a - b overflows, since the latter is how the operation is realized), which means not only that it inherently won't work if the subtraction was supposed to wrap, but I'm also not sure how it will interact with overflowed addition and then compare-to-zero. (all this is architecture-dependent, but typical, and true of the x86 specifically) – Random832 May 20 '16 at 05:19
  • I'm thinking specifically of x86. That unsigned compare I wrote will compile to `add eax, ebx` / `jns`. I chose `INT_MAX` as the cutoff for a reason: because it lets the compiler check the sign flag to detect unsigned results that have their MSB set. Yup, I just tested this, and I was right: [gcc uses add/js](https://godbolt.org/g/rTRqBh), because [`add`](http://www.felixcloutier.com/x86/ADD.html) sets all flags according to the result. See also http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt. Anyway, we're not checking for overflow, we're checking the result > or < than 0. – Peter Cordes May 20 '16 at 05:26
  • In this case it sets the sign flag, but it also sets the overflow flag, and the signed comparison to zero is going to use jge, not jns. The "negative flag" I referred to was therefore actually SF^OF, not SF. – Random832 May 20 '16 at 05:33
  • But that's not how you test for `(a+b) < 0` signed. You need to check flags that just depend on the result, not on whether there was overflow to get there or not. Checking the `ge` condition after an add tests `-a >= b` or the other way around. That's why gcc branches on the `signed` condition, not the `ge` condition, because it needs to avoid depending on the OF flag to get correct results. Check that godbolt link again: I edited to add a signed-compare version that works with `-fno-strict-overflow`, and compiles to identical code. – Peter Cordes May 20 '16 at 05:37
5

Integer overflow behaviour is not defined by the C++ standard. This means that any implementation of C++ is free to do whatever it likes.

In practice this means: whatever is most convenient for the implementor. And since most implementors treat int as a twos-complement value, the most common implementation nowadays is to say that an overflowed sum of two positive numbers is a negative number which bears some relation to the true result. This is a wrong answer and it is allowed by the standard, because the standard allows anything.

There is an argument to say that integer overflow ought to be treated as an error, just like integer division by zero. The '86 architecture even has the INTO instruction to raise an exception on overflow. At some point that argument may gain enough weight to make it into mainstream compilers, at which point an integer overflow may cause a crash. This also conforms with the C++ standard, which allows an implementation to do anything.

You could imagine an architecture in which numbers were represented as null-terminated strings in little-endian fashion, with a zero byte saying "end of number". Addition could be done by adding byte by byte until a zero byte was reached. In such an architecture an integer overflow might overwrite a trailing zero with a one, making the result look far, far longer and potentially corrupting data in future. This also conforms with the C++ standard.

Finally, as pointed out in some other replies, a great deal of code generation and optimization depends on the compiler reasoning about the code it generates and how it would execute. In the case of an integer overflow, it is entirely licit for the compiler (a) to generate code for addition which gives negative results when adding large positive numbers and (b) to inform its code generation with the knowledge that addition of large positive numbers gives a positive result. Thus for example

if (a+b>0) x=a+b;

might, if the compiler knows that both a and b are positive, not bother to perform a test, but unconditionally to add a to b and put the result into x. On a twos-complement machine, that could lead to a negative value being put into x, in apparent violation of the intent of the code. This would be entirely in conformity with the standard.

nugae
  • 499
  • 2
  • 5
  • 1
    There are actually a fair number of applications where trapping on overflow or silently yielding an arbitrary value without side-effects would both be acceptable; unfortunately, hyper-modern UB has evolved far beyond that. If programmers could rely upon overflow having constrained consequences, code which could accept those consequences could be more efficient than code which had to prevent overflow at all costs, but on modern compilers the mere act of testing `(a+b > 0)` can arbitrarily *and retroactively* alter the values of `a` and `b`. That's what's scary. – supercat May 21 '16 at 04:58
3

It is undefined what value is represented by the int. There's no 'overflow' in memory like you thought.

renefritze
  • 2,167
  • 14
  • 25
  • Thank you, I understand that this has nothing to do with memory corruption now :) – Vinz May 19 '16 at 14:25
  • 2
    It's worse than that. The compiler might [optimize based on the assumption that signed overflow never happens.](http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html) (e.g. `i+1 > i` is *always* true). This can lead to things other a single variable having an undefined value. – Peter Cordes May 19 '16 at 16:04
  • @PeterCordes: Do you agree with my description of compiler behavior in the 1990s--an expression like `(i+1 > i)` might arbitrarily yield 0 or yield 1 when `i==INT_MAX`, but those were the only two possible behaviors? IMHO, allowing that expression to arbitrarily yield 0 or 1, but saying that `((int)(i+1) > i)` must perform a wrapping computation, would allow more efficient code in many cases than either requiring that compilers always use wrapping, or requiring that programmers explicitly convert values to unsigned in cases where code needs to stay on the rails for all input values... – supercat May 19 '16 at 21:56
  • ...but where it wouldn't matter whether the computation behaved in wrapping fashion or not [e.g. if the expression had been `i+j > k`, and `j` and `k` were loop invariants, a compiler may be able compute `k-j` outside the loop and then compare `i` to that, but not if the programmer uses unsigned math to guard against overflow. – supercat May 19 '16 at 22:00
  • @supercat: I thought that on an 90's compiler targeting a 2's complement machine, a `while(i+1 > i) i++;` loop would be guaranteed not to be infinite, and that that was a new thing? How do you figure that a 90's compiler might evaluate that expression to 0 when `i==INT_MAX`? – Peter Cordes May 20 '16 at 04:05
  • @supercat: I agree that C would benefit a lot from having syntax to explicitly request wrapping semantics. I like Rust's approach, where it's an error for any normal operation to wrap (unsigned carry or signed overflow), and the primitive types like `i32` have [`checked_add`](https://doc.rust-lang.org/std/primitive.i32.html#method.checked_add) / `overflowing_add` to detect errors, and `wrapping_add` to explicitly declare that you want wrapping. Similar functions exist for all ops, like shifts, rotates, div, etc. There's also `saturating_add/sub/mul`, `count_ones`, `bswap`, to/from BE, ... – Peter Cordes May 20 '16 at 04:12
  • @PeterCordes: Some compilers in the 1990s had "loop-optimization" options which were noted in the documentation as changing semantics in situations involving overflow. Having checked-overflow types would be helpful, but I'd suggest that the best proper semantics would be to have overflow set an error flag (the implementation could choose to use errno or something else) in cases where it produced an observably wrong result, but allow compilers to behave as though signed types could hold extra precision when convenient. Among other things, if a computation would end up being ignored... – supercat May 20 '16 at 14:27
  • ...a compiler would have the option to simply omit the computation, rather than having to perform it purely to determine if it overflowed. The latter point would be a *major* advantage of compiler overflow support, since use of manual overflow checks would require a compiler to perform useless computations. In any case, I think there was enormous value in having compilers guarantee that integer computations other than division will never disrupt the behavior of unrelated code; as far as I can that was until ~2005 honored by *all* microcomputer compilers which didn't explicitly trap overflow. – supercat May 20 '16 at 14:32
  • @supercat: Have you seen Agner Fog's new ISA proposal? The discussion thread covered a few ideas for better integer-overflow detection mechanisms, including having ["sticky" FP-style exception flags that you can check after a whole chain of calculations to see if any of them wrapped](http://www.agner.org/optimize/blog/read.php?i=421#478). Without something like that, you need the compiler to emit a conditional branch after every integer instruction. The perf hit from that would probably only be acceptable in debugging/testing. – Peter Cordes May 20 '16 at 16:25
  • @PeterCordes: I'll have to take a look, though a good compiler should be able to provide sticky overflow support efficiently even without new hardware features *if the semantics make setting the flag optional in cases where results would be arithmetically correct*. For example, given the task of adding 1024 `int32_t` values to yield an `int32_t` total, optimized ARM7-TDMI code could add per-step overflow-checking with zero per-item overhead; optimized x64 code could compute a 64-bit total with zero per-item overhead. I don't think zero per-item overhead should be unacceptably high, do you? – supercat May 20 '16 at 16:32
  • @PeterCordes: I don't think manufacturers are apt to new features to an instruction set without language support (a sticky overflow flag would be fairly complicated to implement); I think it would be better to start by having such a feature in a language (with the semantics I describe, a compiler could often implement it to be much more efficient than anything that could be specified in user code without such a feature). If the feature becomes popular and its performance could be improved enough to justify the hardware cost, then manufacturers could accommodate it. – supercat May 20 '16 at 17:50
  • @supercat: that's true, there's a chicken-and-egg problem here. You're probably right, that software can more easily come first, and implement overflow checking on existing hardware. – Peter Cordes May 20 '16 at 18:14
  • @PeterCordes: Do you like the idea of semantics like I described [an error flag that code can clear before doing calculations and check afterward, with the proviso that if results exceed "int" range but all calculations will end up yielding numerically correct results anyway, the value of the overflow flag would be Unspecified?] I've never seen the latter provision in a language spec, but it could offer a huge speed boost. – supercat May 20 '16 at 18:21
  • @supercat: yeah, an error flag that you clear before calculations and check afterwards is good. IDK about that Unspecified caveat, though. There are some cases where you'd want that, but probably other cases where you want to check your inputs with a small calculation to make sure they won't overflow sometime during a big (unchecked) calculation later. I guess with those semantics, you wouldn't code that way, and would have to use checked arithmetic for the loop you actually cared about. – Peter Cordes May 20 '16 at 18:30
  • 1
    @PeterCordes: The purpose you describe could be facilitated by an intrinsic which would set the overflow flag if an rvalue exceeds the range of its type. Such a thing would only be necessary on rare occasions; letting programmers specify it on those occasions would make it possible to improve performance in the more common cases where all that's needed is an overall "Did anything go wrong in during this large calculation"? – supercat May 20 '16 at 18:38