标准对下面的代码有什么看法?
#include <iostream>
#include <chrono>
#include <thread>
int main()
{
static_assert(sizeof(int) == sizeof(float));
using namespace std::chrono_literals;
auto pi = new int{10};
std::thread t([pi]() {
for (int i = 0; i < 3; i++) {
std::cout << *pi << '\n';
std::this_thread::sleep_for(1s);
}
});
std::this_thread::sleep_for(1s);
auto pf = ::new (pi) float{};
*pf = -1.0;
t.join();
}
我特别好奇如何(或者,是否有可能)应用[basic.life]/7和[basic.life]/8说
类似地,在对象的生命周期开始之前但在对象将占用的存储空间已分配之后,或者在对象的生命周期结束之后并且在对象占用的存储空间被重用或释放之前
和
如果在对象的生命周期结束之后,在对象占用的存储空间被重用或释放之前,在原对象占用的存储位置创建一个新对象
分别,鉴于[basic.life]/11说
在本节中,“之前”和“之后”指的是“<a href="https://timsong-cpp.github.io/cppwp/n4659/intro.multithread#def:happens_before" rel="nofollow noreferrer" >发生在之前”的关系。
这是否意味着如果线程没有“看到”int
对象生命周期结束,它可以访问它,就好像它还活着一样?
起初我认为该程序存在数据竞争:
如果程序的执行包含两个潜在的并发冲突操作,则程序的执行包含数据竞争,其中至少一个不是原子的,并且两者都不会在另一个之前发生,除了下面描述的信号处理程序的特殊情况。任何此类数据竞争都会导致未定义的行为。
但是,没有相互冲突的动作。根据“冲突”的定义:
如果其中一个修改内存位置而另一个读取或修改相同的内存位置,则两个表达式求值会发生冲突。
以及“内存位置”的定义:
内存位置要么是标量类型的对象,要么是所有具有非零宽度的相邻位域的最大序列。
reads through*pi
与 store through 不冲突*pf
,因为这些左值表示不同的对象,因此表示不同的内存位置。
我觉得程序必须有UB,但看不到在哪里。