gcc 我是否依赖于未定义的行为,还是这是一个编译器bug?

s5a0g9ez  于 2023-06-23  发布在  其他
关注(0)|答案(1)|浏览(106)

我写了一个类似于std::unique_ptr的类,但是对象是用placement new构造的。本质上,这个类的析构函数只调用所包含对象的析构函数,而不释放内存。(我现在知道,这可以用unique_ptr和一个自定义的删除器来完成,但我仍然想知道为什么我得到奇怪的结果。
当我使用GCC和任何优化(-O 1和更高)编译它时,最后一个Assert失败。如果我将优化设置为-O 0,错误就消失了。

#include <cassert>
#include <memory>

template<typename T>
class unique_placed_ptr {
public:
    template<typename... Args>
    unique_placed_ptr(void * Address, Args && ... Arguments) {
        m_Object = new (Address) T{std::forward<Args>(Arguments)...};
    }
    
    ~unique_placed_ptr() {
        m_Object->~T();
    }
private:
    T * m_Object;
};

template<typename T, typename... Args>
auto make_unique_placed_ptr(void * Address, Args && ... Arguments)
-> unique_placed_ptr<T> {
    return unique_placed_ptr<T>{Address, std::forward<Args>(Arguments)...};
}

int g_Global = 0;

struct TestObject {
public:
    TestObject() {
        m_Local = 1;
    }
    
    ~TestObject() {
        m_Local = 2;
    }
    
    int m_Local;
};

int main() {
    assert(g_Global == 0);
    { // frame for deconstruction
        auto UniquePlacedPtr = make_unique_placed_ptr<TestObject>(&g_Global);
        
        assert(g_Global == 1);
    }
    assert(g_Global == 2);
}

显然,这个例子被精简和修改以突出问题。
我在godbolt中对比过:
https://godbolt.org/z/55qn8fbYE
Clang从6.0.0版本开始没有任何问题。GCC能够从6.1版开始编译,但无法Assert(包括trunk)。
GCC似乎正在做一些“析构函数省略”,因为在析构函数中更改局部变量应该是无关紧要的。但如果存在某种形式的内存别名(如placement new),那么它就不是了。
那么,我的程序是否依赖于未定义的行为,或者这是GCC中的一个优化错误,应该报告?

ikfrs5lh

ikfrs5lh1#

您的代码通过阅读生存期已结束的int来调用未定义的行为。来自cppreference(最后一个项目符号):
对象的生存期在以下情况下结束:

  • 如果是非类类型,则销毁对象(可能通过伪析构函数调用),或者
  • 如果是类类型,则析构函数调用启动,或者
  • 对象占用的存储器被释放,或者由未嵌套在其中的对象重新使用。

你正在阅读一个int,它已经不复存在了。

相关问题