GotW #102: Assertions and “UB” (Difficulty: 7/10)

This special Guru of the Week series focuses on contracts. Now that we have considered assertions, postconditions, and preconditions in GotWs #97-101, let’s pause and reflect: To what extent does a failed contract imply “UB”… either the Hidden Dragon of Undefined Behavior, or the Crouching Tiger of Unspecified Behavior?

JG Question

1. Briefly, what is the difference among:

(a) undefined behavior

(b) unspecified behavior

(c) implementation-defined behavior

Guru Questions

2. For each of the following, write a short function of the form:

/*...function name and signature...*/
{
    assert( /*...some condition about the parameters...*/ );
    /*...do something with parameters...*/;
}

where if the assertion is not checked and is false then the effect:

(a) is always undefined behavior

(b) possibly results in undefined behavior

(c) is never undefined or unspecified behavior

3. Explain how your answers to Questions 1 and 2 do, or do not, correspond with each other.

4. BONUS: Describe a valuable service that a tool could perform for assertions that satisfy the requirement in 2(a), that is not possible for other assertions.

6 thoughts on “GotW #102: Assertions and “UB” (Difficulty: 7/10)

  1. I meant the feature that I suggested as answer to question 4, that it would exacerbate the consequences of invoking UB, because optimizers don’t always exploit all of it: https://blog.llvm.org/2011/05/what-every-c-programmer-should-know_21.html
    Enabling the feature would tell the compiler to assume that every assertion failure that would always lead to UB proves that it won’t happen, so that it can be exploited without restraint. That’s why it can’t be enabled on default.

    > it is meaningless to talk about the binaries invoking UB.
    So how would you express tersely that binaries behave as expected due to a compiler flag, in spite of UB in source, if mentioning UB is a no-no?

  2. I disagree. One of the flaws of C++ is how much behaviour is UB (for historic and optimization reasons). Removing some of that UB is desirable. I don’t see how the gcc option can possibly “exacerbate the consequences of invoking UB”. Finally, it is meaningless to talk about the binaries invoking UB. Binaries have a behaviour; source can have “behaviour which is not defined by this international standard”.

  3. @Martin Bonner
    In that case source code could invoke UB, but the binaries wouldn’t.
    Also such a feature would surely exacerbate the consequences of invoking UB, so I felt a need to suggest that it might not be a good idea after all, which is the intent of the question. At least it shouldn’t be the default. Most source isn’t accompanied by correctness proofs, and even the best programmers can invoke UB unintentionally. Even the original Cfront had cases of testing for 0 after use: https://www.i-programmer.info/programming/cc/9212-finding-bugs-in-the-first-c-compiler-what-does-bjarne-think.html
    There is also prior experience of something like it. MSVC had a custom assertion macro that fed the condition to the optimizer in release builds. I think it was eventually removed because of problems caused by it (albeit partly due to coders not understanding what it did).

  4. “no sane person would release a program that could invoke UB, right?” Wrong. It is perfectly acceptable to release a program with UB according to the C++ standard *if* your implementation provides a definition for the behaviour. (For example there’s gcc flag which forces signed integer arithmetic to be 2s-complement with wrap round. Integer overflow is still UB according to the standard, but the implementation defines it.)

  5. 1. There’s a long SO post about this: https://stackoverflow.com/q/2397984. Here are my rules of thumb:
    b) The compiler could toss a coin each time to choose between alllowed options.
    c) The compiler is consistent, at least as long as compilation options don’t change.
    a) License to kill.

    2.

    cakeSliceAngle(int eaters) { assert(eaters); return 360/eaters;}// a
    cakeSliceAngle(int eaters) { assert(0<eaters); return 360/eaters;}// b
    cakeSliceAngle(int eaters) { assert(eaters<9); return 360/eaters;}//c

    The only requirement for c is that nothing unexpected happens when the condition is *false* :P

    3. The compiler can see this, so it may assume that passed objects are never 0, which could kill someone.

    4. In release builds the condition could be fed to the optimiser. It can only be done when violating it is always UB, lest a non-UB program might be broken. Better optimizations save power, so it is valuable.
    It is safe because obvously no sane person would release a program that could invoke UB, right? Riiight?!?!

  6. If you don’t mind my asking, w.r.t. the bonus question, are 2(b,c) equivalent to the halting problem, but (a) isn’t?

Comments are closed.