3

IsoCpp.org offers a FAQ regarding placement new:

The example they provide is:

#include <new>        // Must #include this to use "placement new"
#include "Fred.h"     // Declaration of class Fred
void someCode()
{
  char memory[sizeof(Fred)];     // Line #1
  void* place = memory;          // Line #2
  Fred* f = new(place) Fred();   // Line #3 (see "DANGER" below)
  // The pointers f and place will be equal
  // ...
}

Wouldn't the above code violate C++'s strict aliasing rule since place and memory are different types, yet reference the same memory location?

(I know that pointers of type char can alias any other type, but here we seem to have a void* aliasing a char*, which is not allowed from what I understand?)

I suspect that most memory allocators would also violate the strict aliasing rule in a similar manner. What is the proper way to comply with the strict aliasing rule when using placement new?

Thank you

digitale
  • 645
  • 4
  • 13
  • The standard actually makes an exemption for `char` pointers in strict-aliasing. Because they are used so often to make buffers of a precise size to hold various structs and whatnot, compilers have to assume that any other pointer can alias a `char*`. Given that `void*` can't be deferenced generally compilers will only complain that if you cast a void pointer to another type and use it you could run into aliasing issues. Your main problem is `memory` and `f` aliasing, but given that `memory` is a `char*` you are alright. – RyanP May 14 '16 at 18:44
  • @RyanP, thanks for the clarification; I'm quite new to strict aliasing and didn't realize one has to actually dereference to invoke UB. I was aware of the `char*` aliasing exemption, but I thought it only went one-way-- meaning `char*` can alias (and deref) any type `T`, but type `T` can only alias and deref a `char*` type if `T` itself is of type `char*`? – digitale May 14 '16 at 20:29

2 Answers2

5

What is the proper way to comply with the strict aliasing rule when using placement new?

The correct way is to use std::aligned_storage. That code sample doesn't guarantee correct storage alignment for Fred, so it should not be used.

The correct way to do this is:

#include <new>         // For placement new
#include <type_traits> // For std::aligned_storage

struct Fred {
  // ...
};

void someCode() {
  std::aligned_storage<sizeof(Fred), alignof(Fred)>::type memory;
  // Alternatively, you can remove the "alignof(Fred)" template parameter if you
  // are okay with the default alignment, but note that doing so may result in
  // greater alignment than necessary and end up wasting a few bytes.
  Fred* f = new(&memory) Fred();
}

Wouldn't the above code violate C++'s strict aliasing rule since place and memory are different types, yet reference the same memory location?

Now, as for your concerns about aliasing between f, place, and memory in the original code, note that there isn't any aliasing violation. The strict aliasing rule means that you can't "dereference a pointer that aliases an incompatible type". Since you can't dereference a void* (and it's legal to convert a pointer to/from a void*), there's no risk of place causing a strict aliasing violation.

Community
  • 1
  • 1
Cornstalks
  • 37,137
  • 18
  • 79
  • 144
  • Unless you're thinking of some special exception in the standard that allows aliasing with `std::aligned_storage::type`, this doesn't answer the question, this should just be a comment. –  May 14 '16 at 18:48
  • @hvd: The first half of my answer is intended to cover the "What is the proper way to comply with the strict aliasing rule when using placement new?" question with a demo of how to correctly use placement new. But you're right that I didn't address the other parts of the question, which is why I've added the second half of my answer. – Cornstalks May 14 '16 at 18:52
  • That doesn't look bad, but I don't think it's quite right yet: whether the pointer conversion is valid is independent of whether the resulting pointer can safely be dereferenced. There could be an aliasing violation if any code refers to the bytes in `memory` directly. But if there isn't, there's no aliasing, so there can't be an aliasing violation either. –  May 14 '16 at 18:55
  • @hvd: Are you sure that accessing `memory` would be a strict aliasing violation? If `Fred` is a POD type that is trivially constructible/destructible, then wouldn't it be legal to `memcpy` a `Fred` into `memory`? – Cornstalks May 14 '16 at 19:01
  • It's iffy. Accessing the bytes as `char` objects would be allowed, there's a special exception for that. But doing so through the `memory` array attempts to access an object which no longer exists, because it was overwritten by a `Fred` object. I'm pretty sure you'd need to obtain the pointer through `f`. –  May 14 '16 at 19:07
  • thanks for the insight on aligned_storage. I understand now that it is not a strict aliasing violation to have two different type pointers refer to the same memory as long as the pointers don't deref...but what about `memory` and `f`? They are both pointing to the same memory (`f` was constructed in the buffer allocated to `memory`) Doesn't that construction count as accessing `memory`? I will definitely want to use `f` (the newly constructed object) in ways that involve dereferencing (e.g. calling methods on f, etc) Does this make sense? – digitale May 14 '16 at 20:41
5

There isn't a problem because the code doesn't refer to *place. Just having the pointers be equal doesn't cause UB - it's indirecting through both of them which is forbidden.

For example, the following is legal:

 struct A {int x;} a;
 struct B {} *pb = reinterpret_cast<B*>(&a);
 A* pa = reinterpret_cast<A*>(pb);

Refer to *pb and you have violated the strict aliasing rules.

In your particular example of course, you can't write *place because that would yield an lvalue of type void which isn't allowed.

Note also the point Cornstalks makes: The example really needs to be using std::aligned_storage because there is no guarantee that memory is properly aligned for a Fred object. In practise this often doesn't matter because you will have allocated the memory for the placement new with something like new or malloc (which do return suitably aligned storage).