相关疑难解决方法(0)

reinterpret_cast vs严格别名

我正在阅读有关严格别名的内容,但它仍然有点模糊,我无法确定定义/未定义行为的界限.我发现最详细的帖子集中在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)

c++ strict-aliasing language-lawyer reinterpret-cast

8
推荐指数
2
解决办法
974
查看次数

"洗衣"是否通过指针算法传播?

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?

c++ undefined-behavior c++17

7
推荐指数
1
解决办法
422
查看次数

C++20 中的 std::launder 用例

[1]

\n\n

是否有任何情况不需要将p0593r6添加到 C++20 ( \xc2\xa7 6.7.2.11对象模型[intro.object] ) std::launder,而需要 C++17 中的相同用例std::launder,或者它们是完全正交?

\n\n
\n\n

[2]

\n\n

[ptr::launder]规范中的示例是:

\n\n
struct 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\n
Run Code Online (Sandbox Code Playgroud)\n\n

@Nicol Bolas在这个 SO 答案中给出了另一个例子,使用指向有效存储但类型不同的指针:

\n\n …

c++ language-lawyer c++20 stdlaunder

7
推荐指数
1
解决办法
2009
查看次数

这是严格的别名违规吗?任何类型指针都可以作为字符指针的别名吗?

我仍在努力理解严格别名允许和不允许的内容。这个具体的例子是否违反了严格的别名规则?如果不是,为什么?是因为我将新的不同类型放入 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)

c++ strict

7
推荐指数
2
解决办法
216
查看次数

我可以通过placement-new覆盖const对象吗?

基本生活/8告诉我们,我们可以在一个对象的生命周期结束后,使用它所占用的存储空间来创建一个新的对象,并使用它原来的名称来引用它,除非:

\n
\n
    \n
  • 原始对象的类型不是 const 限定的,并且,如果是类类型,则不包含任何类型为 const 限定的非静态数据成员或引用类型,并且 [...]
  • \n
\n
\n

强调我的

\n

但是,就在其下方,我们可以看到一条注释:

\n
\n
    \n
  • 如果不满足这些条件,则可以通过调用从表示其存储地址的指针获得指向新对象的指针std\xe2\x80\x8b::\xe2\x80\x8blaunder
  • \n
\n
\n

这解释了的目的std::launder。我们可以有一个类类型const,并使用placement-new 来创建一个具有不同内部值的新对象。

\n

让我惊讶的是第一句话的第一部分。它清楚地表明,如果存储是const(不一定包含const成员,但整个对象被声明为const),我们不能用它来引用一个新对象,但这可能意味着std::launder可以修复它。

\n

但我们如何创建这样的对象呢?最简单的例子失败了:

\n
const auto x = 5;\nnew (&x) auto(10);\n
Run Code Online (Sandbox Code Playgroud)\n

这是因为我们不能用作const void*新放置的缓冲区。我们可以const_cast做到,但抛弃“真实”const性就是未定义的行为。我相信这里也是如此。

\n

如果只是禁止使用const对象作为放置新缓冲区,我会理解,但如果是这样,那么第一个引用中强调的部分的目的是什么?我们能否真正利用重用const对象的存储重用于不同的对象吗?

\n

c++ language-lawyer stdlaunder

6
推荐指数
1
解决办法
399
查看次数

在单独的线程中与类型的构造函数并行运行成员函数是否是未定义的行为?

这是你不应该做的场景,但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

5
推荐指数
1
解决办法
165
查看次数

std::ranges::swap() 和 std::swap() 有什么区别?

在C++20中,有两个swap函数模板:std::ranges::swap(T&&, U&&)std::swap(T&, T&)

我只是好奇:

他们两个有什么区别?

c++ standards stl c++20 std-ranges

4
推荐指数
1
解决办法
650
查看次数

使用placement new操作符时如何检查是否超出范围?

在下面的代码中

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)

c++ valgrind placement-new c++17

2
推荐指数
1
解决办法
167
查看次数

我在哪里可以找到std :: launder真正做的事情?

我试图理解它是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++ pointer-aliasing reinterpret-cast c++17

-2
推荐指数
1
解决办法
531
查看次数