【问题标题】:On std::launder, GCC and clang: why such a different behavior?关于 std::launder、GCC 和 clang:为什么会有如此不同的行为?
【发布时间】:2022-12-14 07:31:57
【问题描述】:

我正在修改the cppreference launder 网页上给出的示例。

下面显示的示例表明,要么我误解了某些东西并引入了 UB,要么某处存在错误,或者 clang 是为了松懈或者太好了。

  1. 在 doit1() 中,我认为 GCC 所做的优化是不正确的(函数返回 2)并且没有考虑到我们使用放置新返回值这一事实。
  2. 在 doit2() 中,我相信代码也是合法的,但是对于 GCC,没有生成代码?

    在这两种情况下,clang 都提供了我期望的行为。在 GCC 上,这取决于优化级别。我试过 GCC 12.1,但这不是唯一显示此行为的 GCC 版本。

    #include <new>
    
    struct A {
        virtual A* transmogrify(int& i);
    };
    
    struct B : A {
        A* transmogrify(int& i) override {
            i = 2;
            return new (this) A;
        }
    };
    
    A* A::transmogrify(int& i) {
        i = 1;
        return new (this) B;
    }
    
    static_assert(sizeof(B) == sizeof(A), "");
    
    int doit1() {
        A i;
        int n;
        int m;
    
        A* b_ptr = i.transmogrify(n);
    
        // std::launder(&i)->transmogrify(m);    // OK, launder is NOT redundant
        // std::launder(b_ptr)->transmogrify(m); // OK, launder IS     redundant
                       (b_ptr)->transmogrify(m); // KO, launder IS redundant, we use the return value of placment new
    
        return m + n; // 3 expected, OK == 3, else KO
    }
    
    int doit2() {
        A i;
        int n;
        int m;
    
        A* b_ptr = i.transmogrify(n);
    
        // b_ptr->transmogrify(m); // KO, as shown in doit1
        static_cast<B*>(b_ptr)->transmogrify(m); // VERY KO see the ASM, but we realy do have a B in the memory pointed by b_ptr
    
        return m + n; // 3 expected, OK == 3, else KO
    }
    
    int main() {
        return doit1();
        // return doit2();
    }
    

    代码可在:https://godbolt.org/z/43ebKf1q6

【问题讨论】:

  • @LanguageLawyer 您引用的段落不适用,因为A 有一个微不足道的析构函数。双重如此,因为 A 类型的基类子对象也占用相同的存储位置。上面关于 B 对象如何不能透明地替换 A 对象的段落是问题所在
  • @Artyer 已删除,同意琐碎。不同意更换。

标签: c++ g++ language-lawyer clang++ stdlaunder


【解决方案1】:

UB 来自访问 A i; 以在范围末尾调用析构函数而不洗指针。这可以让编译器假设i在那之前没有被存储重用破坏。

你需要更像:

    alignas(B) std::byte storage[sizeof(B)];
    A& i = *new (storage) A;

    // ...

    static_cast<B*>(std::launder(&i))->~B();
    // or: b_ptr->~B();
    // or: simply don't call the destructor

【讨论】:

  • UB 来自访问 A i; 以在范围末尾调用析构函数我认为标准并没有说使用i调用析构函数。
猜你喜欢
  • 2020-10-16
  • 1970-01-01
  • 2021-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-17
  • 2012-04-21
相关资源
最近更新 更多