7

I was messing with placement new, and made this code:

#include <iostream>
#include <functional>
#include <new>

int main()
{
    char memory[sizeof(int)]; //memory to hold type
    auto ref = std::ref(*new (memory) int{5}); //allocating int in memory, and make a reference to it
    std::cout << ref << std::endl;
    new (memory) unsigned{6}; //"overwrite"
    std::cout << ref << std::endl;
}

The output is 5 then 6, but is it well defined? If so, would it be okay if I used float as second type instead?

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
xinaiz
  • 7,744
  • 6
  • 34
  • 78
  • As far as lifetime goes, you're dereferencing a dangling pointer. Can't go trawling through the standard for it right now, though, so there's a chance of a corner case there. – chris Feb 24 '17 at 20:48
  • 1
    You are accessing an `unsigned int` through a `std::reference_wrapper`, that is a wrapper around a reference to `int`. Therefore we can say that you are accessing an `unsigned int` through an `int &`. Is it legal? I would say UB. – skypjack Feb 24 '17 at 21:00
  • 1
    Also, the standard says - _A reference shall be initialized to refer to a valid object or function_. Therefore, your attempt to change the underlying object smells definitely. – skypjack Feb 24 '17 at 21:11
  • The implementation here: http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper, says reference_wrapper actually is implemented using a pointer, so there is no reference here. Maybe when accessing it using `operator T& ()`. – marcinj Feb 24 '17 at 21:20
  • 1
    @marcinj I got the definition from [here](http://eel.is/c++draft/refwrap). That being said, the same applies also to pointers, so implementation details seem pointless here. Accessing an object `U` through a `T *` is seldom a good idea (put aside polymorphism, of course). – skypjack Feb 24 '17 at 21:23
  • in case of pointers it could violate strict aliasing rule. But gcc allows `unsigned int` to alias an `int` (https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html). But thats what gcc does, not what standard says (at least to my knowledge). – marcinj Feb 24 '17 at 21:52
  • 1
    Whatever other rules this may violate (and it almost certainly violates one of the object lifetime rules), it doesn't violate [the strict aliasing rule](https://timsong-cpp.github.io/cppwp/basic.lval#8.4). – T.C. Feb 25 '17 at 04:38
  • You should also consider alignment issues. With a simple `char` buffer you have no guarantee that the memory is properly aligned. Already with the `int` you should use [`std::aligned_storage`](http://en.cppreference.com/w/cpp/types/aligned_storage). With multiple types (`int`, `float`...) take a look at [`std::aligned_union`](http://en.cppreference.com/w/cpp/types/aligned_union) – manlio Feb 27 '17 at 09:18

1 Answers1

4

The second placement new-expression reuses the storage of, and therefore ends the lifetime of, the int object created by the first placement new-expression ([basic.life]/1.4).

Since [basic.life]/8 isn't satisfied (due to the type difference), the pointer stored by the reference wrapper doesn't point to the new object, but continues to point to the out-of-lifetime int object. Accessing the object via an lvalue obtained from such a pointer therefore has undefined behavior per [basic.life]/7.


Note that strict aliasing is irrelevant here; that rule explicitly permits accessing an object via a glvalue of "a type that is the signed or unsigned type corresponding to the dynamic type of the object".

T.C.
  • 133,968
  • 17
  • 288
  • 421