84

I don't quite understand why I don't get a division by zero exception:

int d = 0;
d /= d;

I expected to get a division by zero exception but instead d == 1.

Why doesn't d /= d throw a division by zero exception when d == 0?

S.S. Anne
  • 15,171
  • 8
  • 38
  • 76
Valerii Boldakov
  • 1,751
  • 9
  • 19
  • 1
    its a matter of definition what should be the result of `0/0`. I don't know what the c++ standard says, but I am not aware of any case were you get an exception thrown without explicitly asking for – 463035818_is_not_an_ai Aug 23 '19 at 15:15
  • 26
    That’s undefined behavior. – L. F. Aug 23 '19 at 15:15
  • 52
    There's no such thing as a division by zero exception. – πάντα ῥεῖ Aug 23 '19 at 15:15
  • 15
    To clarify some of the comments: when you see a message about a "division by zero exception" that's the operating system telling you that something went wrong. It is **not** a C++ exception. In C++, exceptions are thrown by a `throw` statement. Nothing else (unless you're in undefined-behavior land). – Pete Becker Aug 23 '19 at 15:26
  • 1
    https://en.cppreference.com/w/cpp/language/ub – Jesper Juhl Aug 23 '19 at 15:29
  • 3
    The "zero division error" is an interruption that some CPU define and can be trapped, but it's not always the case. That's why it's an undefined behavior : it completely depends on the target. – user11659763 Aug 23 '19 at 15:33
  • 9
    There's no such thing as "_division by zero exception._" in C++. – Algirdas Preidžius Aug 23 '19 at 15:39
  • possible duplciate: https://stackoverflow.com/questions/4745311/c-division-by-0 – 463035818_is_not_an_ai Aug 23 '19 at 15:41
  • @OP What compiler are you using? – Adrian Mole Aug 23 '19 at 16:16
  • @Adrian I have used `g++ 8.3.0` to get the aforementioned result. – Valerii Boldakov Aug 23 '19 at 16:35
  • Note that on my environment (clang on macOS), the program crashes with a floating-point exception when compiled with no optimizations, and does nothing when compiled with `-O1` (using whatever gibberish was sitting in the register) – Brennan Vincent Aug 24 '19 at 08:20
  • 6
    @user11659763 _"That's why it's an undefined behavior : it completely depends on the target."_ - That's not what undefined behaviour means _at all_; what you're describing is [implementation-defined behaviour](https://clc-wiki.net/wiki/C_language:Terms:Implementation-defined_behaviour). Undefined behaviour is a much, much stronger statement. – marcelm Aug 24 '19 at 12:56
  • As usual, the answer is "volatile" – curiousguy Aug 24 '19 at 16:42
  • `8.5.5 Multiplicative operators` -> `The binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second. If the second operand of / or % is zero the behavior is undefined.` (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4713.pdf) – Sebastian Mach Aug 26 '19 at 06:48

5 Answers5

112

C++ does not have a "Division by Zero" Exception to catch. The behavior you're observing is the result of Compiler optimizations:

  1. The compiler assumes Undefined Behavior doesn't happen
  2. Division by Zero in C++ is undefined behavior
  3. Therefore, code which can cause a Division by Zero is presumed to not do so.
    • And, code which must cause a Division by Zero is presumed to never happen
  4. Therefore, the compiler deduces that because Undefined Behavior doesn't happen, then the conditions for Undefined Behavior in this code (d == 0) must not happen
  5. Therefore, d / d must always equal 1.

However...

We can force the compiler to trigger a "real" division by zero with a minor tweak to your code.

volatile int d = 0;
d /= d; //What happens?

So now the question remains: now that we've basically forced the compiler to allow this to happen, what happens? It's undefined behavior—but we've now prevented the compiler from optimizing around this undefined behavior.

Mostly, it depends on the target environment. This will not trigger a software exception, but it can (depending on the target CPU) trigger a Hardware Exception (an Integer-Divide-by-Zero), which cannot be caught in the traditional manner a software exception can be caught. This is definitely the case for an x86 CPU, and most other (but not all!) architectures.

There are, however, methods of dealing with the hardware exception (if it occurs) instead of just letting the program crash: look at this post for some methods that might be applicable: Catching exception: divide by zero. Note they vary from compiler to compiler.

Xirema
  • 19,889
  • 4
  • 32
  • 68
  • With or without the 'volatile', a run with MSVC/VS-2019 gives: exited with code -1073741676! – Adrian Mole Aug 23 '19 at 15:42
  • … switching to clang-cl, though, I get a 'random' value for `d` (nearly always 14684554) but no crash?? – Adrian Mole Aug 23 '19 at 15:43
  • 25
    @Adrian Both of those are perfectly ok since the behaviour is undefined. Literally *anything* is OK. – Jesper Juhl Aug 23 '19 at 15:50
  • 9
    "Division by Zero in C++ is undefined behavior" -> note that the compiler can't make this optimisation for floating point types under IEE754. It must set d to NaN. – Bathsheba Aug 23 '19 at 15:52
  • @Adrian if i understand the answer correctly, thats the whole point of it. When you got `1` as result this was due to the compiler realizing that `d /= d;` is either `1` or UB. UB cannot happen in correct code, so it must be `1`. If the compiler doesnt do this optimization anything can happen (aka UB) – 463035818_is_not_an_ai Aug 23 '19 at 15:55
  • Interestingly this optimisation only occurs in gcc when d is an int, not when it's a double: https://godbolt.org/z/XeFAAA – Richard Hodges Aug 23 '19 at 15:59
  • 7
    @RichardHodges a compiler operating under IEEE754 is not allowed to make the optimisation for a double: NaN must be produced. – Bathsheba Aug 23 '19 at 16:12
  • @Bathsheba: This is wrong. IEEE754 states _"seven invalid arithmetic operations shall deliver a NaN unless they are trapped"_. Which means that it is very much possible, and legitimate not to deliver a Nan". The optimization is therefore certainly allowed **unless** the compiler explicitly disabled floating point exceptions or is otherwise certain that the hardware doesn't support/deliver them. – Damon Aug 24 '19 at 09:19
  • 2
    @formerlyknownas: It's not a matter of "optimizing away the UB" -- it's still the case that "anything can happen"; it's just that producing `1` is a perfectly valid anything. Getting 14684554 must be because the compiler optimizes _even further_ -- it propagates the initial `d==0` condition and can therefore conclude not only "this is either 1 or UB" but in fact "this is UB, period". Therefore it doesn't even bother to produce code that loads the constant `1`. – hmakholm left over Monica Aug 24 '19 at 13:36
  • Specifically for x86 (signal) vs. ARM (no signal), and how/why the x86 hardware exception turns into a POSIX SIGFPE in the case where we do get division overflow, see [Why does integer division by -1 (negative one) result in FPE?](//stackoverflow.com/q/46378104) – Peter Cordes Aug 24 '19 at 21:47
  • BTW, the reason you need `volatile` even with "optimization disabled" is that GCC `-O0` does still optimize some *within* a single expression. But `volatile` tells it that each read of `d` is separate and can't be assumed to give the same value. – Peter Cordes Aug 24 '19 at 21:50
  • I wonder why volatile case is not optimized. Of course you need a read and a write, but why not compile it to(read d, write 1) – RiaD Aug 25 '19 at 11:38
  • @RiaD as Peter just said - the compiler needs to issue two reads and one write (each side of the division), and each read might return a different value. – Riking Aug 25 '19 at 20:40
  • 1
    People are always suggesting volatile to prevent optimization, but what constitutes a volatile read or write is implementation-defined. – philipxy Aug 26 '19 at 09:20
  • @RonJohn Well, `X/X = 1` for every X except 0 - for which it is undefined. In undefined behavior **any** result is a "correct" result, because being undefined means that there are no rules for right or wrong. So the compiler could generate standard compliant code that said: `if (X != 0) result = 1 else format hard drive and destroy computer`. Or it could just shorten all of that (and be a bit nicer to OP's computer) and just generate code that returns 1 always. – Frodyne Aug 26 '19 at 09:21
  • @Frodyne if that's what "undefined" means, and if they let 0/0 be undefined (as opposed to causing an error, or letting the h/w throw an error) then the WG21 committee should commit mass hari kari. – RonJohn Aug 26 '19 at 11:03
  • 1
    @RonJohn Undefined is not a value, it's a behavior. C++ code that includes a division by zero is wrong, according to the standard, so the compiler doesn't care what it gets set to. Whether that's 1, 0, some random number, or the program just crashes - they're all correct as defined by the standard. – scatter Aug 26 '19 at 13:00
  • Worth emphasising, the compiler can do *anything*. It could make a program that hires criminals to kidnap your loved ones & post you a ransom letter and it would still be correct by the standard, as far as I know. Of course, it probably wouldn’t be a very popular compiler... – Ashley Davies Aug 27 '19 at 17:52
  • with gcc I am getting the same result i.e. d == 1 even if I disable optimization using -O0 switch. With macOS compiler I am getting floating point exception. – Pankaj Jangid Aug 28 '19 at 11:22
  • @Jangid Certain optimizations have to be disabled with specific flags, not just through `-O0`. It's like Copy Ellision, which will usually get performed even when optimizations are disabled. I don't know which flag specifically (if any) control optimizations around this behavior. – Xirema Aug 28 '19 at 14:17
39

Just to complement the other answers, the fact that division by zero is undefined behavior means that the compiler is free to do anything in cases where it would happen:

  • The compiler may assume that 0 / 0 == 1 and optimize accordingly. That's effectively what it appears to have done here.
  • The compiler could also, if it wanted to, assume that 0 / 0 == 42 and set d to that value.
  • The compiler could also decide that the value of d is indeterminate, and thus leave the variable uninitialized, so that its value will be whatever happened to be previously written into the memory allocated for it. Some of the unexpected values observed on other compilers in the comments may be caused by those compilers doing something like this.
  • The compiler may also decide to abort the program or raise an exception whenever a division by zero occurs. Since, for this program, the compiler can determine that this will always happen, it can simply emit the code to raise the exception (or abort execution entirely) and treat the rest of the function as unreachable code.
  • Instead of raising an exception when division by zero occurs, the compiler could also choose to stop the program and start a game of Solitaire instead. That also falls under the umbrella of "undefined behavior".
  • In principle, the compiler could even issue code that caused the computer to explode whenever a division by zero occurs. There is nothing in the C++ standard that would forbid this. (For certain kinds of applications, like a missile flight controller, this might even be considered a desirable safety feature!)
  • Furthermore, the standard explicitly allows undefined behavior to "time travel", so that the compiler may also do any of the things above (or anything else) before the division by zero happens. Basically, the standard allows the compiler to freely reorder operations as long as the observable behavior of the program is not changed — but even that last requirement is explicitly waived if executing the program would result in undefined behavior. So, in effect, the entire behavior of any program execution that would, at some point, trigger undefined behavior is undefined!
  • As a consequence of the above, the compiler may also simply assume that undefined behavior does not happen, since one permissible behavior for a program that would behave in an undefined manner on some inputs is for it to simply behave as if the input had been something else. That is, even if the original value of d was not known at compile time, the compiler could still assume that it's never zero and optimize the code accordingly. In the particular case of the OP's code, this is effectively indistinguishable from the compiler just assuming that 0 / 0 == 1, but the compiler could also, for example, assume that the puts() in if (d == 0) puts("About to divide by zero!"); d /= d; never gets executed!
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
30

The behaviour of integer division by zero is undefined by the C++ standard. It is not required to throw an exception.

(Floating point division by zero is also undefined but IEEE754 defines it.)

Your compiler is optimising d /= d to, effectively d = 1 which is a reasonable choice to make. It's allowed to make this optimisation since it's allowed to assume there is no undefined behaviour in your code - that is d cannot possibly be zero.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 3
    It's important to be extra clear, that also something else could happen, IOW this behavior can't be relied on. – hyde Aug 23 '19 at 15:22
  • I think simply emphasizing "You compiler" would be enough. – François Andrieux Aug 23 '19 at 15:23
  • 2
    When you say it's reasonable for the compiler to assume "that is `d` cannot possibly be zero," do you also assume that the compiler doesn't see the line: `int d = 0;` ?? :) – Adrian Mole Aug 23 '19 at 15:37
  • 6
    Compiler sees it, but probably doesn't care. The extra code complexity required in the already crazy complex compiler for an edge case like this probably isn't worth it. – user4581301 Aug 23 '19 at 15:47
  • 1
    @user4581301 Taking both together allows it to detect a poisoned branch, allowing it to prune much more code. So it would be useful. – Deduplicator Aug 23 '19 at 15:58
  • 1
    @Adrian absolutely. Undefined behaviour applies backwards as well as forwards. At the point d /= d is reached, it can assume d is never zero. – Bathsheba Aug 23 '19 at 16:11
  • 3
    So if you wrote "int d = 0; if (d == 0) printf("d = zero\n"); d /= d; " the compiler can remove the printf as well. – gnasher729 Aug 24 '19 at 23:08
  • 1
    @gnasher729 The compiler can remove all the code on that branch. – Bathsheba Aug 25 '19 at 07:14
1

The simplest way to understand what happens is to see the assembly output

int divide(int num) {
    return num/num;
}

will generate for x86-64

divide(int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     eax, 1
        pop     rbp
        ret

As you can see there are no divide operations here, but we have mov eax, 1.

Here is a link to reproduce: https://godbolt.org/z/MbY6Wqh4T

0

Note that you can have your code generate a C++ exception in this (and other cases) by using boost safe numerics. https://github.com/boostorg/safe_numerics

Robert Ramey
  • 1,114
  • 8
  • 16