我正在阅读有关严格别名的内容,但它仍然有点模糊,我无法确定定义/未定义行为的界限.我发现最详细的帖子集中在C.所以如果你能告诉我这是否允许以及自C++ 98/11以来发生了什么变化,那将是很好的...
#include <iostream>
#include <cstring>
template <typename T> T transform(T t);
struct my_buffer {
char data[128];
unsigned pos;
my_buffer() : pos(0) {}
void rewind() { pos = 0; }
template <typename T> void push_via_pointer_cast(const T& t) {
*reinterpret_cast<T*>(&data[pos]) = transform(t);
pos += sizeof(T);
}
template <typename T> void pop_via_pointer_cast(T& t) {
t = transform( *reinterpret_cast<T*>(&data[pos]) );
pos += sizeof(T);
}
};
// actually do some real transformation here (and actually also needs an inverse)
// ie this …Run Code Online (Sandbox Code Playgroud) 如P0532R0中所述,在下面的用例中std::launder必须使用以避免未定义的行为(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);
Run Code Online (Sandbox Code Playgroud)
但是在缓冲区中有多个对象的情况下会发生什么(如果X在向量中推送2 ,则会发生这种情况,清除向量然后再推送两个对象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);//???
Run Code Online (Sandbox Code Playgroud)
最后一个断言是正确的,还是p0[1]仍然调用UB?
[1]
\n\n是否有任何情况不需要将p0593r6添加到 C++20 ( \xc2\xa7 6.7.2.11对象模型[intro.object] ) std::launder,而需要 C++17 中的相同用例std::launder,或者它们是完全正交?
[2]
\n\n[ptr::launder]规范中的示例是:
\n\nstruct X { int n; };\nconst X *p = new const X{3};\nconst int a = p->n;\nnew (const_cast<X*>(p)) const X{5}; // p does not point to new object ([basic.life]) because its type is const\nconst int b = p->n; // undefined behavior\nconst int c = std::launder(p)->n; // OK\nRun Code Online (Sandbox Code Playgroud)\n\n@Nicol Bolas在这个 SO 答案中给出了另一个例子,使用指向有效存储但类型不同的指针:
\n\n …我仍在努力理解严格别名允许和不允许的内容。这个具体的例子是否违反了严格的别名规则?如果不是,为什么?是因为我将新的不同类型放入 char* 缓冲区吗?
template <typename T>
struct Foo
{
struct ControlBlock { unsigned long long numReferences; };
Foo()
{
char* buffer = new char[sizeof(T) + sizeof(ControlBlock)];
// Construct control block
new (buffer) ControlBlock{};
// Construct the T after the control block
this->ptr = buffer + sizeof(ControlBlock);
new (this->ptr) T{};
}
char* ptr;
T* get() {
// Here I cast the char* to T*.
// Is this OK because T* can alias char* or because
// I placement newed a T …Run Code Online (Sandbox Code Playgroud) 基本生活/8告诉我们,我们可以在一个对象的生命周期结束后,使用它所占用的存储空间来创建一个新的对象,并使用它原来的名称来引用它,除非:
\n\n\n\n
\n- 原始对象的类型不是 const 限定的,并且,如果是类类型,则不包含任何类型为 const 限定的非静态数据成员或引用类型,并且 [...]
\n
强调我的
\n但是,就在其下方,我们可以看到一条注释:
\n\n\n\n
\n- 如果不满足这些条件,则可以通过调用从表示其存储地址的指针获得指向新对象的指针
\nstd\xe2\x80\x8b::\xe2\x80\x8blaunder
这解释了的目的std::launder。我们可以有一个类类型const,并使用placement-new 来创建一个具有不同内部值的新对象。
让我惊讶的是第一句话的第一部分。它清楚地表明,如果存储是const(不一定包含const成员,但整个对象被声明为const),我们不能用它来引用一个新对象,但这可能意味着std::launder可以修复它。
但我们如何创建这样的对象呢?最简单的例子失败了:
\nconst auto x = 5;\nnew (&x) auto(10);\nRun Code Online (Sandbox Code Playgroud)\n这是因为我们不能用作const void*新放置的缓冲区。我们可以const_cast做到,但抛弃“真实”const性就是未定义的行为。我相信这里也是如此。
如果只是禁止使用const对象作为放置新缓冲区,我会理解,但如果是这样,那么第一个引用中强调的部分的目的是什么?我们能否真正利用重用const对象的存储重用于不同的对象吗?
这是你不应该做的场景,但https://timsong-cpp.github.io/cppwp/class.cdtor#4指出:
成员函数,包括虚函数([class.virtual]),可以在构造或销毁([class.base.init])期间调用。
如果并行调用这些函数,这是否成立?也就是说,忽略竞争条件,如果A正在构造过程中,并且frobme在构造函数被调用之后的某个时间点被调用(例如在构造过程中),那仍然是定义的行为吗?
#include <thread>
struct A {
void frobme() {}
};
int main() {
char mem[sizeof(A)];
auto t1 = std::thread([mem]() mutable { new(mem) A; });
auto t2 = std::thread([mem]() mutable { reinterpret_cast<A*>(mem)->frobme(); });
t1.join();
t2.join();
}
Run Code Online (Sandbox Code Playgroud)
作为一个单独的场景,有人还向我指出, 的A构造函数可以创建多个线程,这些线程可能会A在构造完成之前调用成员函数函数,但是这些操作的顺序会更易于分析(您知道在构造函数中生成线程之后才会发生竞争)。
c++ constructor race-condition object-lifetime language-lawyer
在下面的代码中
struct alignas(8) SimpleChar {
SimpleChar(char c_) : c(c_) {}
char c;
};
int main() {
char slab[10] = {'\0'};
// call to 'SimpleChar::SimpleChar(char)' too
SimpleChar* c0 = new (slab) SimpleChar('a');
SimpleChar* c1 = new (slab + 8) SimpleChar('b');
SimpleChar* c2 =
new (std::launder(reinterpret_cast<char*>(slab + 80))) SimpleChar('d'); // But how to detect the wrong usage?
std::cout << c2->c << std::endl; // d
SimpleChar* c3 = new (slab + 180) SimpleChar('e'); // But how to detect the wrong usage?
std::cout << …Run Code Online (Sandbox Code Playgroud) 我试图理解它是std::launder做什么的,我希望通过查找一个示例实现,它将是清楚的.
我在哪里可以找到一个示例实现std::launder?
当我查看lbic ++时,我看到了类似的代码
template<typename _Tp>
[[nodiscard]] constexpr _Tp*
launder(_Tp* __p) noexcept
{ return __builtin_launder(__p); }
Run Code Online (Sandbox Code Playgroud)
这让我觉得这是另一个编译魔术函数.
这个函数__builtin_launder可能会做什么,它是否只是添加一个标记来抑制编译器有关别名的警告?
是否可以理解std::launder来讲__builtin_launder,或者它只是更多的编译器魔术(挂钩)?
c++ ×9
c++17 ×3
c++20 ×2
stdlaunder ×2
constructor ×1
standards ×1
std-ranges ×1
stl ×1
strict ×1
valgrind ×1