摘自 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)
[直播]
(我假设这一行:
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)
)。