Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the "laundry" propagated by pointer arithmetic?

As explained in P0532R0, in the folowing use case std::launder must be used to avoid undefined behavior (UB):

struct X{
  const int i;
  x(int i):i{i}{}
  };

unsigned char buff[64];
auto p = new(buff) X(33);
p->~X();
new(buff) X(42);
p = std::launder(p);
assert(p->i==42);

But what happen in the case where more than one object is on the buffer (this is exactly what would happen if one pushes 2 X in a vector, clears the vector and then pushes two new X):

unsigned char buff[64];
auto p0 = new(buff) X(33);
auto p1 = new(p0+1) X(34);
p1->~X();
p0->~X();
new(buff) X(42);
new(p0+1) X(43);
p0 = std::launder(p0);
assert(p0->i==42);
assert(p0[1].i==43);//???

Is the last assertion correct, or p0[1] still invokes UB?

like image 210
Oliv Avatar asked Sep 02 '17 13:09

Oliv


People also ask

What does pointer arithmetic do?

Pointer arithmetic provides the programmer with a single way of dealing with different types: adding and subtracting the number of elements required instead of the actual offset in bytes.

Are pointers broken the privacy of variables?

A private member is no different from a public member, or any other type of instance. So, yes, you can take pointers to them, and return them.


1 Answers

Your code invokes UB, but not for launder reasons. It's because p0[1].i is itself UB.

Yes, really ([Expr.Add]/4):

When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the expression P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j ) point to the (possibly-hypothetical) element x[i + j] if 0 ≤ i + j ≤ n ; otherwise, the behavior is undefined. Likewise, the expression P - J points to the (possibly-hypothetical) element x[i − j] if 0 ≤ i − j ≤ n; otherwise, the behavior is undefined.

An object that is not an array element is considered to belong to a single-element array for this purpose; see 8.3.1. A pointer past the last element of an array x of n elements is considered to be equivalent to a pointer to a hypothetical element x[n] for this purpose; see 6.9.2.

[] when applied to a pointer means to do pointer arithmetic. And in the C++ object model, pointer arithmetic can only be used on pointers to elements in an array of the type being pointed to. You can always treat an object as an array of length 1, so you can get a pointer to "one past the end" of the single object. Thus, p0 + 1 is valid.

What is not valid is accessing the object stored at that address though the pointer obtained via p0 + 1. That is, p0[1].i is undefined behavior. This is just as UB before laundering it as after.

Now, let's look at a different possibility:

X x[2];
x[1].~X(); //Destroy this object.
new(x + 1) X; //Construct a new one.

So let's ask some questions:

Is x[1] UB? I would say... no, it is not UB. Why? Because x[1] is not:

a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object

x points to the array and the first element of that array, not the second element. Therefore, it does not point to the original object. It is not a reference, nor is it the name of that object.

Therefore, it does not qualify for the restrictions stated by [basic.life]/8. So x[1] should point to the newly constructed object.

Given that, you don't need launder at all.

So if you're doing this in a way that's legal, then you don't need launder here.

like image 105
Nicol Bolas Avatar answered Sep 24 '22 08:09

Nicol Bolas