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

guykilcj  于 12个月前  发布在  其他
关注(0)|答案(1)|浏览(181)

摘自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?


SIGSEGV在试图销毁拥有自己地址的V对象时发生。

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)


为什么拥有自身的vector上的allocator_traits::destroy()会触发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)


[LIVE]

zour9fqk

zour9fqk1#

(我假设这条线:

auto front = v.front();

字符串
这是一个输入错误,因为v.front()是一个默认构造的V对象,您可以从中复制。这意味着该行本质上是auto front = V{}。您的意思是auto& front = v.front()
&front + 1&front + 2&front + 3是指向空向量的指针,它们可以被销毁。
如果你试图销毁&front指向的东西,这是一个有4个元素的向量。最后三个元素是那些空向量,它们被销毁没有问题。
但是front的第一个元素是front本身。这是一个未定义的行为,因为你会在一个已经被销毁的对象上调用析构函数,但实际上它会导致一个无限循环,因为它只会再次调用析构函数并递归地试图销毁同一个向量(以及导致segfault的堆栈溢出)。
如果你只是简单地释放它,没有析构函数被调用,所以没有无限循环。如果vector包含其他分配内存的vector,你可能会泄漏内存,因为这些vector的析构函数也不会被调用。
正如您已经尝试过的,“修复”这个问题的方法是打破“所有权”循环,比如将其移动到一个新的向量(V{} = std::move(front)/V{}.swap(front))。

相关问题