Are these two evaluations conflicting if corresponding memory locations are unclear?

11 hours ago 1
ARTICLE AD BOX

[intro.races] p2 says:

Two expression evaluations conflict if one of them

2.1 modifies ([defns.access]) a memory location ([intro.memory]) or 2.2 starts or ends the lifetime of an object in a memory location

and the other one

2.3 reads or modifies the same memory location or 2.4 starts or ends the lifetime of an object occupying storage that overlaps with the memory location.

[intro.memory] p3 says:

A memory location is the storage occupied by the object representation of either an object of scalar type that is not a bit-field or a maximal sequence of adjacent bit-fields all having nonzero width.

Consider this example:

#include <thread> alignas(int) unsigned char buffer[8]; int main() { auto ptr = new (buffer) int{}; std::jthread t1([&]() { new (buffer) char{}; // #1 }); std::jthread t2([&]() { auto n = *ptr; // #2 }); }

In the above bullets, only 2.3 mentions reading memory locations, so only 2.3 applies to #2, 2.2 applies to #1.

However, #1 starts the lifetime of an object of type char at the memory location that has 1 byte. Instead, #2 reads the memory location that has 4 bytes(assuming sizeof(int)==4). Memory locations with different sizes, reasonably, cannot be said to be the same memory location.

From another perspective, #1 also reuses the storage with 1 byte; however, the int object occupies storage with 4 bytes. So, whether #1 causes the end of the lifetime of the int object is unclear according to [basic.life] p2:

The lifetime of an object o of type T ends when:

[...] the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]).

In this example, #1 uses only 1 byte of storage; however, the int object occupies 4 bytes. Seems we cannot say the storage occupied by the int object is reused by #1 to conclude that #1 ends the lifetime of the int object.

Q2

#include <string> #include <thread> alignas(std::string) unsigned char buffer[sizeof(std::string)]; auto ptr = new (buffer) std::string{}; int main() { std::jthread t1([]() { new (&buffer) char{}; // #1 }); std::jthread t2([]() { auto r = ptr->size(); // #2 }); }

In this example, now, the layout of std::string is opaque to us; we don't even know whether there is a private member object occupying the same memory location as the char object created at #1, so how do we determine whether #1 and #2 conflict?

Read Entire Article