Is laundry a common arithmetic for signs?

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

 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 happens when more than one object is in the buffer (this is exactly what happens if 2 X is discarded in the vector, the vector is cleared, and then two new X pressed):

 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 statement correct, or does p0[1] still call UB?

+7
c ++ undefined-behavior c ++ 17
source share
2 answers

Your code calls UB, but not for launder reasons. This is because p0[1].i is UB itself.

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

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

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

[] , when applied to a pointer, means performing pointer arithmetic. And in the C ++ object model, pointer arithmetic can only be used for pointers to elements in an array of the specified type. You can always consider an object as an array of length 1, so you can get a one-way pointer for one object. Thus, p0 + 1 acts.

Access to the object stored at this address is invalid, although the pointer obtained using p0 + 1 . That is, p0[1].i is undefined behavior. It’s like UB before washing it, as after.

Now consider another possibility:

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

So, we ask a few questions:

Is x[1] UB? I would say ... no, this is not UB. What for? Because x[1] not:

pointer pointing to the source object, a link related to the source object, or the name of the source object

x points to the array and the first element of this array, not the second element. Therefore, it does not indicate the source object. This is not a link, and it is not the name of this object.

Therefore, it does not fall under the restrictions specified in [basic.life] / 8. Therefore, x[1] should point to the newly constructed object.

Given that you do not need launder at all.

So, if you do it so legally, you do not need launder here.

+5
source share

The reason std::launder needed, first of all, because of this violation from [basic.life]

If after the object has reached the end of its life and before the repository that the object is busy reusing or released, a new object is created in the storage location where the source object was loaded, a pointer pointing to the source object, a link related to the source object, or the name of the source object will automatically refer to the new object and, as soon as the lifetime of the new object is started, it can be used to manage the new object if: [...]

  • the type of the source object is not a const qualifier and, if the class type does not contain any non-static data member whose type is constant or reference, and [...]

Therefore, without std::launder , p original pointer will not point to the newly constructed object.

If these conditions are not met, a pointer to a new object can be obtained from a pointer that represents its storage address by calling std​::​launder

This is why std::launder does what it does .

From [ptr.launder] , aptly titled "Pointer Optimizer"

If a new object is created in a repository occupied by an existing object of the same type, a pointer to the source object can be used to refer to the new object, unless the type contains constants or reference elements; in the latter cases, this function can be used to obtain a useful pointer to a new object.

Which says that the original pointer cannot be used to refer to the newly created object unless erased.

From what I see, it can be interpreted in both directions (maybe completely wrong).

  • The pointer calculated by the washing method is not the original pointer, so its well-formed
  • Nowhere is it mentioned that the pointer calculated from the laundered cursor is laundered, therefore its UB

I personally think the first should be true, since std::launder is a barrier to pointer optimization.

+2
source share

All Articles