logoalt Hacker News

Veserv • today at 3:37 AM • 2 replies • view on HN

Undefined behavior is just what happens when you violate a mandatory precondition.

if (x > 0) {…}. But what if you entered the body when x <= 0?

if (false) {…}. But what if you execute the body?

These are “impossible”. What happens when the impossible occurs is “undefined”.

When the older standards said signed integer overflow for addition is undefined what they are actually saying is that the real definition of + is:

    int +(int x, int y) { 
      assert(in_range(actual_math_add(x, y), signed_int_min, signed_int_max));
      return machine_add(x, y);
    }
So of course what happens when you get signed overflow is undefined; you should hit that assert and your program should explode and die. You should “never” get to the next instruction.

But, in the interest of performance, “release mode” (which in this case is just any compilation) elides asserts since as a programmer you should not write code that asserts in much the same way that you should not write assert(false) in a normal code path that is supposed to run. Assertions are intended for “impossible” code paths and usually get compiled out in “release mode” though maybe your code is buggy and can actually hit them and then your program goes off the rails because it had a bug.

Put another way, if you did write assert(false) in a regular code path, would you find it unreasonable for the compiler to just delete the code after it? That is what undefined behavior is for.


Replies

drdexebtjl • today at 4:37 AM

This isn’t a good mental model. The compiler isn’t limited to just affecting the code after the undefined behavior. It is allowed to assume undefined behavior never happens for the entire program.

For example, this code with an improper guard:

    if (!p) puts("error");
    printf("%d", *p);
Since the program dereferences p in line 2, and dereferencing null is UB, the compiler is allowed to assume p is never null, so it’s allowed to delete line 1, even though it would have executed before the point where UB would happen.

Even worse, the compiler isn't just allowed to not do things you told it to do, it's also allowed to do anything too.

➕ show 2 replies
rramadass • today at 6:04 AM

Beautifully explained!

UB is a formal tool for the optimizer.

Most people who argue about UB on HN have no clue wth it means and how it differs from Unspecified and Implementation-defined behaviours. The standard already explains expressions/statements and how they relate to sequence-points/sequenced-before/sequenced-after code points which is what is needed to understand the anomalous behaviour above.

Add in a introductory class in numerical analysis w.r.t. accuracy/precision/limits/rounding and the C/C++ programmer has enough knowledge to avoid problems in practice.