11

(*) As far as I know the Standard allows an implementation to modify the operand of the delete operator, however most implementations do not do that.

int* ptr = new int(0);
delete ptr; //delete is allowed to modify ptr, for example set it to 0
std::cout << ptr; // UB?

Acknowledging (*), is the reading of ptr (in the form of printing it) well-defined?

If delete does modify ptr, is it allowed to set a trap value, which would make reading ptr UB?

PoweredByRice
  • 2,479
  • 1
  • 20
  • 26
  • 2
    Note that the operand of `delete` can be an prvalue, which can't be "modified" at all. – aschepler Apr 20 '17 at 01:48
  • @aschepler it might modify only lvalue arguments. E.g. I could imagine a debugging tool that sets freed pointers to some known bit pattern to help identify invalid uses – M.M Apr 20 '17 at 01:52

2 Answers2

16

In C++14 this is implementation-defined behaviour, [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, the deallocation function shall deallocate the storage referenced by the pointer, rendering invalid all pointers referring to any part of the deallocated storage.

Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior.

There is a footnote:

Some implementations might define that copying an invalid pointer value causes a system-generated runtime fault.

This changed since C++11 where the bolded text said "undefined behaviour" and there was no footnote.


So to answer your question, delete ptr; is allowed to set a trap value that would cause a runtime fault for std::cout << ptr. The compiler documentation must specify the behaviour. This is a narrower restriction than UB in which case any unstable behaviour would be permissible.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • the footnote is interesting, even copying the deleted pointer could cause runtime failure. – PoweredByRice Apr 20 '17 at 02:21
  • @PoweredByRice Even doing a useless statement `ptr;` (like `(1+2);`) is I-DB. I think I once read that this used to be UB since some architectures in the past has special "pointer registers", and loading one of those with an invalid pointer could already trigger an exception, even if the pointer wasn't dereferenced, yet. I don't know more than that. – chi Apr 20 '17 at 07:57
  • @chi that's the rationale; now the compiler documentation has to document whether such exceptions are possible on the target – M.M Apr 20 '17 at 09:20
  • @chi what is `(1+2);` ? and how is that I-DB? – PoweredByRice Apr 20 '17 at 13:35
  • @PoweredByRice In C++ (and C), any expression can be a statement e.g. `f(); x++; (1+2); ptr; y--;` is valid syntax. The statement `(1+2);` is no-op. The statement `ptr;` is no-op, provided the pointer is valid, otherwise it is I-DB. – chi Apr 20 '17 at 14:37
  • @chi Unless `ptr` is `volatile`, `ptr;` doesn't access the stored value, valid or not, and the behavior is perfectly well-defined. – T.C. Apr 20 '17 at 21:44
  • @T.C. Ah interesting. The same happens for `int i; i;`, then, I guess? I thought this was already I-DB. – chi Apr 20 '17 at 22:02
  • 1
    @chi in C++ such a thing is called *discarded-value expression* and doesn't undergo lvalue-to-rvalue conversion (see the last section of [this answer](http://stackoverflow.com/a/43468519/1505939) for related discussion) – M.M Apr 20 '17 at 22:07
-1

In this example, std::cout << ptr is NOT undefined behavior by default, because ptr is not being dereferenced at all, so it doesn't matter what its value is actually set to.

By default, the STL does not define an operator<< for int* pointers. It defines:

  • an operator<< for (signed|unsignd) char*, used for printing null-terminated text.

  • a generic operator<< for void*, which simply prints the memory address itself that the pointer is set to, not the data that is being pointed at.

Since int* is implicitly convertible to void*, calling std::cin << ptr is actually calling operator<<(std::cin, (void*)ptr), and so prints the memory address as-is that ptr holds.

The code would have undefined behavior only if your app defines its own operator<< for int* and then tries to dereference the pointer after it has been deleted.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 3
    It does somewhat matter what the pointer is. For example, `void* ptr; std::cout << ptr;` is undefined behavior because of evaluation of an indeterminate value. – aschepler Apr 20 '17 at 01:55
  • The **output** generated by `operator<<` would be undefined since `ptr` is uninitialized, but the **behavior** of `operator<<` itself for `void*` input is well-defined (print as-is whatever address value the `void*` holds). – Remy Lebeau Apr 20 '17 at 01:57
  • Firstly, you can safely assume no crazy operator overload or macros, otherwise all bets are off. Secondly, when we talk about undefined behavior, it's the *behavior*, not *output* that is undefined. Thirdly, what @aschepler said. – PoweredByRice Apr 20 '17 at 02:02
  • aschepeler's code is UB , see C++14 [dcl.init]/12. Note, in C++ use of uninitialized variables is covered by a different section to use of freed pointers, although in C both are covered by the same section – M.M Apr 20 '17 at 02:29
  • 1
    @RemyLebeau two words: Trap Representations. – Alex Celeste Apr 20 '17 at 07:48
  • @Leushenko: IMHO, the authors of the Standard likely intended the phrase "trap representation" to refer only to bit patterns to which an implementation would assign no value; if a type had a defined value for every possible bit pattern, reading an Indeterminate Value of that type would yield one of them (chosen arbitrarily). Modern implementations, however, have behaviors which would be inconsistent with such an explanation. – supercat Apr 20 '17 at 15:29
  • @supercat the C++ standard doesn't even mention "trap representation". Instead it says that (summarized) evaluating indeterminate values is UB, and evaluating invalid pointers is IDB – M.M Apr 20 '17 at 22:08
  • @M.M: I sometimes forget which aspects of the C Standard the C++ standard incorporates explicitly or implicitly by reference; the C Standard used to define "Indeterminate Value" as being "Either an unspecified value or a trap representation". In any case, I was responding to Leushenko's use of the term "trap representation", which I read as implying that implied that behavior would be undefined only because the combination of bits stored in an object might have no valid meaning. – supercat Apr 20 '17 at 22:53