草庐IT

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

coder 2023-11-14 原文

简单的多重继承

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。它是怎么做到的?特定于编译器的功能?

最佳答案

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

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 返回原始指针,返回一个绑定(bind)到类型 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(pointer) 方法。使用自定义删除器时,此方法非常危险,因为调用者有责任提供以正确方式分配的指针。如果使用无效指针调用该方法,编译器将无能为力。


传递基指针

您可能希望将基指针传递给函数让该函数负责释放对象。在这种情况下,您需要使用类型删除 向消费者隐藏对象的类型,同时在内部保留其最派生类型的知识。最简单的方法是使用 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 需要为其控制 block 单独分配(不会使用 custom_malloc )。通过多做一些工作,您可以解决这个问题。您需要创建一个包装 custom_malloc 的自定义分配器和 custom_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();
}

关于c++ - 通过指向非多态类型的基类的指针获取已分配内存的地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43225753/

有关c++ - 通过指向非多态类型的基类的指针获取已分配内存的地址的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  3. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  4. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  5. Ruby Koans about_array_assignment - 非平行与平行分配歧视 - 2

    通过ruby​​koans.com,我在about_array_assignment.rb中遇到了这两段代码你怎么知道第一个是非并行赋值,第二个是一个变量的并行赋值?在我看来,除了命名差异之外,代码几乎完全相同。4deftest_non_parallel_assignment5names=["John","Smith"]6assert_equal["John","Smith"],names7end45deftest_parallel_assignment_with_one_variable46first_name,=["John","Smith"]47assert_equal'John

  6. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  7. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

    我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

  8. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  9. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  10. ruby-on-rails - Enumerator.new 如何处理已通过的 block ? - 2

    我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m

随机推荐