3

Let's say we have this legacy code from C++98:

bool expensiveCheck();

struct Foo;

bool someFunc()
{
    Foo *ptr = 0;
    if( expensiveCheck() )
        ptr = new Foo;

    // doing something irrelevant here
    ...
    if( ptr ) {
        // using foo
    }
    delete ptr;
    return ptr; // here we have UB(Undefined Behavior) in C++11
}

So basically pointer here is used to keep dynamically allocated data and use it as a flag at the same time. For me it is readable code and I believe it is legal C++98 code. Now according to this questions:

Pointers in c++ after delete

What happens to the pointer itself after delete?

this code has UB in C++11. Is it true?

If yes another question comes in mind, I heard that committee puts significant effort not to break existing code in new standard. If I am not mistaken in this case this not true. What is the reason? Is such code considered harmfull already so nobody cares it would be broken? They did not think about consequences? This optimization is so important? Something else?

Harsha J K
  • 197
  • 1
  • 1
  • 17
Slava
  • 43,454
  • 1
  • 47
  • 90
  • Note that new C++11 compilers are unlikely to appear when C++14 exists and C++17 is incoming. How practical an answer do you want? Practically, you should care about actual C++11 compilers and the C++14/17 standard permitted behaviour. – Yakk - Adam Nevraumont May 26 '17 at 00:55
  • 1
    It has always been undefined behavior to use a pointer after deleting it – Barmar May 26 '17 at 00:58
  • @Barmar care to provide quote to standard? – Slava May 26 '17 at 00:58
  • The question you link to has lots of citations. – Barmar May 26 '17 at 01:00
  • @Yakk many people have to use C++98 for various reasons. They may migrate to c++11 and stay there for quite some time. – Slava May 26 '17 at 01:00
  • @Barmar that citations to C++11 and C++14, not C++98 – Slava May 26 '17 at 01:01
  • @AviBerger mind to provide quote from C++98? And some people think that after C++11 value of pointer may change after delete. – Slava May 26 '17 at 01:31
  • This was also undefined behaviour in C++98. Other questions talking about the topic mention C++11 because the behaviour changed between C++11 and C++14 (from undefined to implementation-defined with possible hardware fault). – M.M May 26 '17 at 02:00
  • This looks like useless optimization to me, I very much doubt the compiler can't optimize out a local `bool` if it is legal. – Passer By May 26 '17 at 03:28
  • @PasserBy have you heard about Occam's razor? – Slava May 26 '17 at 03:32
  • @slava How will they uae C++11? They will either use an existing compiler, or a new compiler that supports C++14 or better in C++11 mode. In the first case, we care about existing compilers. In the second, C++14 places more reatrictions on compiler behaviour, it seems unlikely that the freedom C++11 grants to do UB matters. – Yakk - Adam Nevraumont May 26 '17 at 04:21
  • @Slava no, I don't understand your point after reading about it either. Though take a look at [this](https://godbolt.org/g/z31pvI) – Passer By May 26 '17 at 04:26
  • @PasserBy "It is vain to do with more what can be done with fewer" – Slava May 26 '17 at 04:29

1 Answers1

7

Your example exhibits undefined behavior under C++98, too. From the C++98 standard:

[basic.stc.dynamic.deallocation]/4 If the argument given to a deallocation function in the standard library is a pointer that is not the null pointer value (4.10), the deallocation function shall deallocate the storage referenced by the pointer, rendering invalid all pointers referring to any part of the deallocated storage. The effect of using an invalid pointer value (including passing it to a deallocation function) is undefined.33)

Footnote 33) On some implementations, it causes a system-generated runtime fault.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • I believe that "using a pointer" in that context means examining or writing to the memory it points to. Just converting the invalidated pointer to false if it is NULL and true otherwise would not be UB. – L. Scott Johnson May 26 '17 at 02:32
  • Thanks. By the way I beleive I saw in C trick to pass `int` as `void *` which converted back to `int` inside function. Is that code legal in C? C++? – Slava May 26 '17 at 02:33
  • 3
    @L.ScottJohnson That's doubtful. For one thing, C++14 version of that footnote reads: "Some implementations might define that copying an invalid pointer value causes a system-generated runtime fault." Practically speaking, I recall reading somewhere that there exists, or exited, an architecture where merely loading an invalid pointer into CPU register could trap. It had dedicated registers for addresses (distinct from those for data), and used segment/offset addressing, where the segment pointed into segment table. If the table entry was invalid, the CPU raised an exception. – Igor Tandetnik May 26 '17 at 04:04
  • @L.ScottJohnson Second, there's a formal definition of "use" in **[basic.def.odr]/2** (citing from C++98): "An expression is *potentially evaluated* unless either it is the operand of the `sizeof` operator (5.3.3), or it is the operand of the `typeid` operator and does not designate an lvalue of polymorphic class type (5.2.8). An object ... is *used* if its name appears in a potentially-evaluated expression." The expression `ptr` in `return ptr;` is potentially-evaluated, and therefore `ptr` is *used* within the meaning of this clause. – Igor Tandetnik May 26 '17 at 04:08
  • @Slava I'm not really familiar with the C standard. In C++98, this particular notion of "invalid pointer" only applies to dangling pointers - pointers into now-deleted memory. C++11 introduces the concept of a *safely-derived* pointer, and makes it implementation-defined whether a pointer that is not safely-derived is invalid. `reinterpret_cast(42)` is not a danging pointer (so it's not invalid per C++98), and it's not a safely-derived pointer (so it may or may not be invalid per C++11). – Igor Tandetnik May 26 '17 at 04:23
  • @Slava Further, **[expr.reinterpret.cast]** says that it's safe to `reinterpret_cast` from a (valid) pointer to a sufficiently large integer and back, and the result is the original pointer. Casting from an arbitrary integer to a pointer has, once again, implementation-defined behavior. – Igor Tandetnik May 26 '17 at 04:25
  • @IgorTandetnik but it is possible that `reinterpret_cast(42)` would match dangling pointer by binary representation hence reading from it will become UB – Slava May 26 '17 at 04:26
  • 1
    @Slava The representation of the pointer is not important: its pedigree is. There's a (non-normative) note in C++11's definition of safely-derived pointer to this effect: "Note: the effect of using an invalid pointer value ... is undefined, see 3.7.4.2. This is true even if the unsafely-derived pointer value might compare equal to some safely-derived pointer value." Basically, the behavior is undefined not so much because it's expected the CPU might choke on a bad value at run-time, but because an aggressive optimizer may make certain assumptions at compile-time that end up not holding. – Igor Tandetnik May 26 '17 at 04:36
  • @Slava In any case, the wording saying any use of a dangling pointer is UB is basically an oversight in C++11, fixed in C++14 (where only double-deallocation and dereferencing of a dangling pointer is UB; any other use is implementation-defined). – Igor Tandetnik May 26 '17 at 04:41
  • 2
    "Safely-derived" is only relevant on implementations with strict pointer safety, which is an empty set. – T.C. May 26 '17 at 06:11