为什么我不能“销毁”“自有”的CRTP向量,但仍然可以释放其地址?

问题描述 投票:0回答:1

摘自 Björn Fahller 在 2023 年 CPP 会议上的闪电演讲。=> youtu.be/LKKmPAQFNgE

这是关于如何在不接触

new
甚至
malloc
的情况下强制 C++ 泄漏内存。

    struct V : vector<V> {};
    auto v = V{};
    v.emplace_back();
    v.swap(v.front());  // v representation becomes uint64_t[3]{ 0x0, 0x0, 0x0},
                        // so the vector allocation gets lost because no refs are left on the scope.

所以我想知道是否可以手动销毁它。

    struct V : vector<V> {};
    auto v = V{};
    v.emplace_back();
    v.emplace_back();
    v.emplace_back();
    v.emplace_back();

    auto front = v.front();

    v.swap(v.front());
    
    using allocator = std::allocator<V>;
    using atraits = std::allocator_traits<allocator>;
    auto a = front.get_allocator();
    atraits::destroy(a, &front + 1);   // Ok
    atraits::destroy(a, &front + 2);   // Ok
    atraits::destroy(a, &front + 3);   // Ok
    //  atraits::destroy(a, &front);   // error SIGSEGV
    atraits::deallocate(a, &front, 4); // still seems Ok?
当尝试销毁拥有自己地址的 V 对象时,会发生

SIGSEGV

0x1796320 : 0x1796320          (alloc_begin_ptr)  // It owns itself!!!
0x1796328 : 0x1796380 (one_pass_content_end_ptr)
0x1796330 : 0x1796380   (one_pass_alloc_end_ptr)

0x1796338 : 0x0          (alloc_begin_ptr)
0x1796340 : 0x0 (one_pass_content_end_ptr)
0x1796348 : 0x0   (one_pass_alloc_end_ptr)

0x1796350 : 0x0          (alloc_begin_ptr)
0x1796358 : 0x0 (one_pass_content_end_ptr)
0x1796360 : 0x0   (one_pass_alloc_end_ptr)

0x1796368 : 0x0          (alloc_begin_ptr)
0x1796370 : 0x0 (one_pass_content_end_ptr)
0x1796378 : 0x0   (one_pass_alloc_end_ptr)

所以我尝试将其移至堆栈。看起来效果不错。

    struct V : vector<V> {};
    auto v = V{};
    v.emplace_back();
    v.emplace_back();
    v.emplace_back();
    v.emplace_back();

    auto front = v.front();

    v.swap(v.front());
    
    auto v2 = std::move(front);

没有任何对象拥有自己。


0x7ffc44d02b20 : 0x927320          (alloc_begin_ptr) // v2 on stack
0x7ffc44d02b28 : 0x927380 (one_pass_content_end_ptr)
0x7ffc44d02b30 : 0x927380   (one_pass_alloc_end_ptr)

0x927320 : 0x0          (alloc_begin_ptr)
0x927328 : 0x0 (one_pass_content_end_ptr)
0x927330 : 0x0   (one_pass_alloc_end_ptr)

0x927338 : 0x0          (alloc_begin_ptr)
0x927340 : 0x0 (one_pass_content_end_ptr)
0x927348 : 0x0   (one_pass_alloc_end_ptr)

0x927350 : 0x0          (alloc_begin_ptr)
0x927358 : 0x0 (one_pass_content_end_ptr)
0x927360 : 0x0   (one_pass_alloc_end_ptr)

0x927368 : 0x0          (alloc_begin_ptr)
0x927370 : 0x0 (one_pass_content_end_ptr)
0x927378 : 0x0   (one_pass_alloc_end_ptr)

为什么拥有自身的

allocator_traits::destroy()
上的
vector
会触发
SIGSEGV

    //  atraits::destroy(a, &front);   // error SIGSEGV

0x1796320 : 0x1796320          (alloc_begin_ptr)  // It owns itself!!!
0x1796328 : 0x1796380 (one_pass_content_end_ptr)
0x1796330 : 0x1796380   (one_pass_alloc_end_ptr)

[直播]

c++ memory-leaks stdvector destroy allocator
1个回答
0
投票

(我假设这一行:

auto front = v.front();

是一个拼写错误,因为

v.front()
是您从中复制的默认构造的
V
对象。这意味着该线本质上是
auto front = V{}
。你的意思是
auto& front = v.front()

&front + 1
&front + 2
&front + 3
是指向空向量的指针,这些可以被销毁。

如果您试图破坏

&front
指向的内容,则这是具有 4 个元素的向量。最后三个元素是那些空向量,被销毁是没有问题的。
front
的第一个元素是
front
本身。这是未定义的行为,因为您将在已经被销毁的对象上调用析构函数,但实际上它会导致无限循环,因为它只会再次调用析构函数并递归地尝试销毁相同的向量(以及堆栈溢出,从而导致段错误)。

如果您只是释放它,则不会调用析构函数,因此不会出现无限循环。如果向量保存了分配内存的其他向量,则可能会泄漏内存,因为这些向量的析构函数也不会被调用。

正如您所尝试的那样,“修复”此问题的方法是打破“所有权”循环,例如将其移动到新向量(

V{} = std::move(front)
/
V{}.swap(front)
)。

© www.soinside.com 2019 - 2024. All rights reserved.