C++返回static_cast指针导致的双重释放<Base>

C++ double free caused by returning a static_cast<Base> pointer

本文关键字:释放 gt lt Base static 返回 cast 指针 C++      更新时间:2023-10-16

我有一个看起来像的工厂类

class Base;
class Derived;
class Factory {
std::vector<std::shared_ptr<Derived>> m_vector;
std::shared_ptr<Base> create() {
std::shared_ptr<Derived> p = std::make_shared<Derived>();
m_vector.push_back(p);
return static_cast<std::shared_ptr<Base>>(p.get());
}
};

class Foo {
void doStuff() {
std::shared_ptr<Base> m_p = m_factory->create();
// ....
m_p = m_factory->create(); // here the code crashed for double free
};

其中m_vector是Factory的成员。当返回的指针将create函数更改为时,我遇到了"double free"崩溃

std::shared_ptr<Base> Factory::create() {
std::shared_ptr<Derived> p = std::make_shared<Derived>();
m_vector.push_back(p);
return std::dynamic_pointer_cast<Base>(p);
}

解决了问题。我知道dynamic_pointer_cast创建了一个与p共享引用计数的指针,因此只要p仍包含在向量中,稍后删除返回的基指针就不会导致资源释放。然而,我仍然不明白为什么当存储在m_p中的指针被覆盖时,代码会崩溃。此时,引用计数应该为0,这将释放资源(因为引用计数不与工厂向量中的指针共享)。因此,当代码在某个时刻引用工厂持有的指针p时,我预计这会导致问题。然而,资源似乎已经第二次被释放了。因此,第一次必须是当create函数的堆栈展开时。为什么?

示例;

#include "memory"
#include "iostream"
struct Foo {
};
struct FooTwo : public Foo {
};
struct Bar {
std::shared_ptr<FooTwo> m = std::make_shared<FooTwo>();
std::shared_ptr<Foo> getFoo()
{
std::shared_ptr<FooTwo> p = std::make_shared<FooTwo>();
// std::shared_ptr<FooTwo> p = std::shared_ptr<FooTwo>(new FooTwo());
m = p;
return std::shared_ptr<Foo>(p.get());
}
};
int main()
{
Bar b;
std::shared_ptr<Foo> d = b.getFoo();
std::cout << "Here 1" << std::endl;
d = nullptr;
std::cout << "Here 2" << std::endl;
return 0;
}

在您的代码中,行

return static_cast<Base>(p.get())

不会编译。

  1. 您转换为值类型,而不是指针类型
  2. 缺少结尾半克隆

当您发布问题时,请确保您编写的代码能够编译您应该提供MCVE(https://stackoverflow.com/help/mcve)。

你的代码或多或少相当于:

std::shared_ptr<Derived> p = std::make_shared<Derived>();
auto *raw_pointer = p.get();
auto *base = static_cast<Base *>(raw_pointer);
std::shared_ptr<Base> ret_value(base);

因此,您可以从单个指针创建两个不同的std::shared_ptr。每一个,都会有一个计数。

你猜怎么着!当第一个shared_ptr超出范围(函数退出)时,对象将被删除。然后将另一个指针移动到CCD_ 3中。

此时,原始对象已被销毁,因此doStuff使用一个已删除的对象,该对象是未定义的行为。

doStuff完成时,m_p也超出范围,并尝试第二次删除对象。

您应该永远不要通过在另一个shared_ptr上调用get来创建shared_ptr

只有当您真正需要一个原始指针时,才应该调用get方法。当你这样做时,你应该确保:

  • 在源shared_ptr超出范围后,您不会使用原始指针,以确保在删除对象后不会使用该对象
  • 您不会手动删除该指针
  • 您不会基于该原始指针创建另一个shared_ptr

更新

如果您想查看计数,可以使用函数use_count进行调试。看见https://en.cppreference.com/w/cpp/memory/shared_ptr/use_count.

这是我对所发生事情的理解。崩溃似乎是由make_shared的内存分配引起的,它在我的编译器上对malloc进行一次调用,并在一个块中为控制块和资源分配空间,首先是控制块,然后是结构。获取原始指针并创建一个新的共享指针将创建一个指向该资源和不同控制块的共享指针。当该指针的引用计数达到0时,它请求释放资源及其自己的控制块,但是,指向资源的指针没有指向内存分配的开始(从第一个共享指针的控制块开始),因此malloc抛出。用共享指针的常规创建替换make shared将对malloc进行两次调用,并为控制块和结构分配一个单独的块,因此稍后的删除不会抛出两次空闲(但第一个指针会指向损坏的资源)