通过指向非多态类型的基类获取已分配内存的地址

时间:2017-04-05 08:24:45

标签: c++ c++11 memory-management

简单的多继承

struct A {};
struct B {};
struct C : A, B {};

或虚拟继承

struct B {};
struct C : virtual B {};

请注意类型不是多态的。

自定义内存分配:

template <typedef T, typename... Args>
T* custom_new(Args&& args...)
{
    void* ptr = custom_malloc(sizeof(T));
    return new(ptr) T(std::forward<Args>(args)...);
}

template <typedef T>
void custom_delete(T* obj)
{
    if (!obj)
        return obj;

    void* ptr = get_allocated_ptr(obj); // here
    assert(std::is_polymorphic_v<T> || ptr == obj);
    obj->~T();
    custom_free(ptr); // heap corruption if assert ^^ failed
}

B* b = custom_new<C>(); // b != address of allocated memory
custom_delete(b); // UB

如何为非多态类型实现get_allocated_ptr?对于多态类型dynamic_cast<void*>完成工作。

或者,我可以检查obj是否是指向基类的指针,因为通过指向基类的指针删除非多态对象是UB。我不知道该怎么做或者根本不可能。

operator delete在这种情况下(例如VC ++)正确释放内存,尽管标准说它是UB。它是如何做到的?编译器特有的功能?

3 个答案:

答案 0 :(得分:5)

实际上你遇到的问题比获取完整对象的地址更严重。考虑这个例子:

struct Base
{
  std::string a;
};

struct Derived : Base
{
  std::string b;
};

Base* p = custom_new<Derived>();
custom_delete(p);

在此示例中,custom_delete实际上会释放正确的地址(static_cast<void*>(static_cast<Derived*>(p)) == static_cast<void*>(p)),但行obj->~T()将调用Base的析构函数,这意味着b字段被泄露了。

所以不要这样做

不是从custom_new返回原始指针,而是返回绑定到类型T并知道如何删除它的对象。例如:

template <class T> struct CustomDeleter
{
  void operator()(T* object) const
  {
    object->~T();
    custom_free(object); 
  }
};

template <typename T> using CustomPtr = std::unique_ptr<T, CustomDeleter<T>>;

template <typename T, typename... Args> CustomPtr<T> custom_new(Args&&... args)
{
  void* ptr = custom_malloc(sizeof(T));
  try
  {
    return CustomPtr<T>{ new(ptr) T(std::forward<Args>(args)...) };
  }
  catch (...)
  {
    custom_free(ptr);
    throw;
  }
}

现在不可能意外地释放错误的地址并调用错误的析构函数,因为调用custom_free的唯一代码知道它正在删除的事物的完整类型。

注意:注意unique_ptr :: reset(指针)方法。使用自定义删除器时,此方法非常危险,因为调用程序上的onus提供了以正确方式分配的指针。如果使用无效指针调用该方法,编译器将无法提供帮助。

绕过基本指针

可能您希望将基本指针传递给函数并且赋予该函数释放对象的责任。在这种情况下,您需要使用 type erasure 来隐藏消费者对象的类型,同时在内部保留其最派生类型的知识。最简单的方法是使用std::shared_ptr。例如:

struct Base
{
  int a;
};

struct Derived : Base
{
  int b;
};

CustomPtr<Derived> unique_derived = custom_new<Derived>();

std::shared_ptr<Base> shared_base = std::shared_ptr<Derived>{ std::move(unique_derived) };

现在你可以自由地传递shared_base,当最终引用被释放时,完整的Derived对象将被销毁,其正确的地址将传递给custom_free。如果您不喜欢shared_ptr的语义,那么使用unique_ptr语义创建类型擦除指针相当简单。

注意:这种方法的一个缺点是shared_ptr需要为其控制块(它不会使用custom_malloc)单独分配。通过更多的工作,你可以解决这个问题。您需要创建自定义分配器,其中包含custom_malloccustom_free,然后使用std::allocate_shared来创建对象。

完整的工作示例

#include <memory>
#include <iostream>

void* custom_malloc(size_t size)
{
  void* mem = ::operator new(size);
  std::cout << "allocated object at " << mem << std::endl;
  return mem;
}

void custom_free(void* mem)
{
  std::cout << "freeing memory at " << mem << std::endl;
  ::operator delete(mem);
}

template <class T> struct CustomDeleter
{
  void operator()(T* object) const
  {
    object->~T();
    custom_free(object); 
  }
};

template <typename T> using CustomPtr = std::unique_ptr<T, CustomDeleter<T>>;

template <typename T, typename... Args> CustomPtr<T> custom_new(Args&&... args)
{
  void* ptr = custom_malloc(sizeof(T));
  try
  {      
    return CustomPtr<T>{ new(ptr) T(std::forward<Args>(args)...) };
  }
  catch (...)
  {
    custom_free(ptr);
    throw;
  }
}

struct Base
{
  int a;
  ~Base()
  {
    std::cout << "destroying Base" << std::endl;
  }
};

struct Derived : Base
{
  int b;
  ~Derived()
  {
    std::cout << "detroying Derived" << std::endl;
  }
};

int main()
{
  // Since custom_new has returned a unique_ptr with a deleter bound to the
  // type Derived, we cannot accidentally free the wrong thing.
  CustomPtr<Derived> unique_derived = custom_new<Derived>();

  // If we want to get a pointer to the base class while retaining the ability
  // to correctly delete the object, we can use type erasure.  std::shared_ptr
  // will do the trick, but it's easy enough to write a similar class without
  // the sharing semantics.
  std::shared_ptr<Base> shared_base = std::shared_ptr<Derived>{ std::move(unique_derived) };

  // Notice that when we release the shared_base pointer, we destroy the complete
  // object.
  shared_base.reset();
}

答案 1 :(得分:1)

您只能使用C执行此操作,obj_1的静态类型必须是多态的。否则请查看此代码:

dynamic_cast

如果您尝试通过指向B的指针删除,则无法知道Tstruct A { int a; }; struct B { int b; }; struct C : A, B {}; B *b1 = new C, *b2 = new B; 是否需要调整为b1。无论如何,您需要b2多态来获取指向大多数派生对象的指针。

答案 2 :(得分:0)

所有结构继承自的虚拟接口如何返回分配对象的指针?我不得不进行一些更改以使代码编译。多继承和虚拟继承案例都有效:

#include <iostream>
#include <type_traits>
#include <cassert>

struct H {
public:
    void* getHeader() { return header; }
    void setHeader(void* ptr) { header = ptr; }
private:
    void* header;
};

// multiple inheritance case
//struct A : public virtual H { int a;};
//struct B : public virtual H { int b;};
//struct C : A, B { };

// virtual inheritance case
struct B : public virtual H { int b; };
struct C : virtual B {};

template <typename T, typename ...Args>
T* custom_new(Args&&... args) {
    void* ptr = malloc(sizeof(T));
    T* obj = new(ptr) T(std::forward<Args>(args)...);
    obj->setHeader(ptr);
    return obj;
}

template <typename T>
void* get_allocated_ptr(T* obj) {
    return obj->getHeader();
}

template <typename T>
void custom_delete(T* obj) {
    void* ptr = get_allocated_ptr(obj); // here
//  assert(std::is_polymorphic<T>::value || ptr == obj); // had to comment
    obj->~T();
    free(ptr); // heap corruption if assert ^^ failed
}

using namespace std;
int main(int argc, char *argv[]) {
    C* c = custom_new<C>(); // b != address of allocated memory
    std::cout << "PTR \t\t= " << c << std::endl;
    auto b = static_cast<B*>(c);
    std::cout << "CAST PTR \t= " << b << std::endl;
    std::cout << "ALLOCATED PTR \t= " << get_allocated_ptr(b) << std::endl; 
    custom_delete(b); // UB
}

您可以使用任一层次结构运行它,输出类似于

PTR             = 0x7f9fd4d00b90
CAST PTR        = 0x7f9fd4d00b98
ALLOCATED PTR   = 0x7f9fd4d00b90

虽然在多重继承的情况下,指针相差16位而不是8位(因为有两个整数)。

通过使用模板启用custom_new以及仅从继承自H接口的结构的其他函数,可以改进此实现。