结束另一个线程中使用的对象的生命周期
Ending the lifetime of an object used in another thread
标准对下面的代码有什么看法?
#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 and [basic.life]/8 saying
Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released
和
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied
分别,因为 [basic.life]/11 是说
In this section, “before” and “after” refer to the “happens before” relation.
这是否意味着如果线程 "see" int
对象生命周期结束,它可以访问它,就好像它还活着一样?
起初我以为程序有一个data race:
The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined behavior.
但是,没有冲突的操作。通过 the definition of "conflict":
Two expression evaluations conflict if one of them modifies a memory location and the other one reads or modifies the same memory location.
和the definition of "memory location":
A memory location is either an object of scalar type or a maximal sequence of adjacent bit-fields all having nonzero width.
通过 *pi
读取不会与通过 *pf
存储冲突,因为这些左值表示 不同的 个对象,因此 不同的 内存位置。
我觉得这个程序必须有UB,但是看不到在哪里。
在计算 ::new (pi) float{};
表达式后在线程中访问 *pi
会导致未定义的行为。
在同一个 [basic.expr]
部分,paragraph 5 说:
A program may end the lifetime of any object by reusing the storage which the object occupies
一旦为 pi
指向的 int
分配的存储被重新用于存储 float,对象 *pi
的生命周期就结束了。第 7 段说当线程访问该值时,您有未定义的行为。
第 6 段中给出了一个示例,该示例与您正在做的事情相同,但没有线程,并评论说这是未定义的行为。
所有这一切的共同结果包括线程中的循环可能会打印相同的值 3 次(如果它在循环之前读取 *pi
一次并显示该值),或者您可能会得到10
一次或两次,则整数表示的二进制值为-1.0f。但这些并不是唯一可能的结果。
[basic.life]/4 The properties ascribed to objects and references throughout this International Standard apply for a given object or reference only during its lifetime.
术语"during"没有正式定义,但将其定义为"after the lifetime starts and before the lifetime ends"是合理的。
现在,happens-after 的反面不是 happens-before,而是“happens-before 或 与 不同步”。因此,happens-before 或 unsynchronized-with 对象生命周期开始的操作,或者 happens-after 或 unsynchronized-with 对象生命周期结束,不执行 "during its lifetime"。在某种程度上,此类操作依赖于 "properties ascribed to" 此对象,它表现出未定义的行为。
基于这些理由,我认为您的示例表现出未定义的行为,因为对对象 *pi
的访问是 不同步的, 其生命周期结束。
标准对下面的代码有什么看法?
#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 and [basic.life]/8 saying
Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released
和
If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied
分别,因为 [basic.life]/11 是说
In this section, “before” and “after” refer to the “happens before” relation.
这是否意味着如果线程 "see" int
对象生命周期结束,它可以访问它,就好像它还活着一样?
起初我以为程序有一个data race:
The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined behavior.
但是,没有冲突的操作。通过 the definition of "conflict":
Two expression evaluations conflict if one of them modifies a memory location and the other one reads or modifies the same memory location.
和the definition of "memory location":
A memory location is either an object of scalar type or a maximal sequence of adjacent bit-fields all having nonzero width.
通过 *pi
读取不会与通过 *pf
存储冲突,因为这些左值表示 不同的 个对象,因此 不同的 内存位置。
我觉得这个程序必须有UB,但是看不到在哪里。
在计算 ::new (pi) float{};
表达式后在线程中访问 *pi
会导致未定义的行为。
在同一个 [basic.expr]
部分,paragraph 5 说:
A program may end the lifetime of any object by reusing the storage which the object occupies
一旦为 pi
指向的 int
分配的存储被重新用于存储 float,对象 *pi
的生命周期就结束了。第 7 段说当线程访问该值时,您有未定义的行为。
第 6 段中给出了一个示例,该示例与您正在做的事情相同,但没有线程,并评论说这是未定义的行为。
所有这一切的共同结果包括线程中的循环可能会打印相同的值 3 次(如果它在循环之前读取 *pi
一次并显示该值),或者您可能会得到10
一次或两次,则整数表示的二进制值为-1.0f。但这些并不是唯一可能的结果。
[basic.life]/4 The properties ascribed to objects and references throughout this International Standard apply for a given object or reference only during its lifetime.
术语"during"没有正式定义,但将其定义为"after the lifetime starts and before the lifetime ends"是合理的。
现在,happens-after 的反面不是 happens-before,而是“happens-before 或 与 不同步”。因此,happens-before 或 unsynchronized-with 对象生命周期开始的操作,或者 happens-after 或 unsynchronized-with 对象生命周期结束,不执行 "during its lifetime"。在某种程度上,此类操作依赖于 "properties ascribed to" 此对象,它表现出未定义的行为。
基于这些理由,我认为您的示例表现出未定义的行为,因为对对象 *pi
的访问是 不同步的, 其生命周期结束。