Jul 15, 2017

[C++] null pointer and memory laundering.

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0532r0.pdf


Check documented measures:
  • Clang: declare your "null pointers" volatile.
  • GCC: use the -fno-delete-null-pointer-checks compilation flag.

'const' in C++ is used by compiler for optimization.
Thus, object that has const data member; and the object is being created at the storage location which the original object occupied should be re-bind to the original object otherwise the use of the const data member is UB.


What is the purpose of std::launder?

std::launder performs memory laundering.

i.e:
struct X { const int n; };
union U { X x; float f; };
...

U u = {{ 1 }};
n is a const variable,
compiler is free to assume that u.x.n shall always be 1.

So what' happens if:
X *p = new (&u.x) X {2};
The new object will have its n member be 2.

What will u.x.n return?

The obvious answer will be 2.
But that's wrong, why? because X *p is new variable.
Will show otherwise in later example.
And thus the compiler is allowed to assume that a truly  const variable

[basic.life]/8 spells out the circumstances when it is OK to access the newly
created object through variables/pointers/references to the old one.
And having a const member is one of the disqualifying factors.

How can we talk about u.x.n properly?
We have to launder our memory:
assert(*std::launder(&u.x.n) == 2); //Will be true.

Memory laundering is used to prevent the compiler from tracing
where we got our object from,
thus forcing it to avoid any optimizations that may no longer apply.

Another of the disqualifying factors is if we change the type of the object:
aligned_storage<sizeof(int), alignof(int)>::type data;
new(&data) int;
int *p = std::launder(reinterpret_cast<int*>(&data));

[basic.life]/8 tells us that, if you allocate a new object in the storage of the old one,
you cannot access the new object through pointers to the old.
std::launder allows us to side-step that.
struct X {
const int n;
double d;
};

X* p = new X{7, 8.8};
new (p) X{42, 9.9};  // request to place a new value into p
int i = p->n; // undefined behavior (i is probably 7 or 42)
auto d = p->d; // also undefined behavior (d is probably 8.8 or 9.9

struct X {
const int n;
double d;
};
X* p = new X{7, 8.8};
p = new (p) X{42, 9.9}; // Note: assign return value of placement new to p
int i = p->n;  // OK, i is guaranteed to be 42
auto d = p->d;  // OK, d is guaranteed to be 9.9
Use std::launder Have to use std::launder() any time you access data where placement new was called. std::launder() works only for pointers to objects for which the lifetime has ended. 
std::launder() does not work on smart pointer type that points to memory that is ended and reused. 
std::launder() does not solve the problem of avoiding undefined behavior in allocator-based containers if you have elements with constant/reference members. 
int i = std::launder(p)->n; // OK, i is 42
int i2 = p->n; // still undefined behavior

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.