【问题标题】:C++ dynamic objects. How is object size determined during runtime?C++ 动态对象。在运行时如何确定对象大小?
【发布时间】:2016-10-05 12:24:13
【问题描述】:

我不明白一件事。例如,我声明类 A 和类 B,它是 A 的子类:

class A {
    public:
        int a;
}

class B : public A {
    public:
        int b;
}

显然,如果我创建 A 或 B 的实例,它们在内存中的大小可以由类型决定。

A instanceA; // size of this will probably be the size of int (property a)
B instanceB; // size of this will probably be twice the size of int (properties a and b)

但是如果我创建动态实例然后释放它们呢?

A * instanceAPointer = new A();
A * instanceBPointer = new B();

这些是不同类的实例,但程序会将它们视为 A 类的实例。使用它们时这很好,但释放它们呢?要释放分配的内存,程序必须知道要释放的内存大小,对吗?

所以如果我写

delete instanceAPointer;
delete isntanceBPointer;

程序如何知道,从每个指针指向的地址开始,它应该释放多少内存?因为显然这些对象有不同的大小,但程序认为它们是 A 类型的。

谢谢

【问题讨论】:

  • 也许这个链接会有所帮助? openrce.org/articles/files/jangrayhood.pdf
  • 实际上,看起来第二个可能会导致内存泄漏,因为它不是多态类。如果类是多态的,编译器能够根据动态类型释放它,无论是使用 RTTI 还是其他方法,并自动释放实际分配的相同数量的内存。但是,如果该类不是多态的,我不相信它能够保证能够正确处理这种情况,因此您应该始终通过正确类型的指针将其删除。
  • @JustinTime 你所说的多态类是指带有虚拟析构函数的类吗?
  • @Justin: delete 永远不会使用 RTTI。虚拟析构函数的存在负责通过 vtable 进行标识。
  • @JustinTime 是的,这就是为什么人们总是说在客户端可能通过多态指针删除的任何类中实现虚拟析构函数,以便可以查找正确的派生 dtor 并执行。

标签: c++ oop object inheritance dynamic-memory-allocation


【解决方案1】:

我假设你知道delete works.

关于delete 如何知道如何清理继承的实例。这就是为什么您在继承上下文中使用 virtual destructor 的原因,否则您将有未定义的行为。基本上,析构函数和其他所有 virtual 函数一样是通过 vtable 调用的。

还记得:C++ 编译器会隐式破坏析构函数中的父类

class A {
    public:
        int a;
    virtual ~A(){}
}

class B : public A {
    public:
        int b;
    ~B() { /* The compiler will call ~A() at the very end of this scope */ }
}

这就是为什么它会起作用;

A* a = new B();
delete a;

通过vtable,析构函数~B()将被delete调用。由于编译器在派生类中隐式插入基类的析构函数调用,A 的析构函数将在~B() 中调用。

【讨论】:

  • 那么,如果我不声明虚拟析构函数,程序可能会崩溃,也可能不会崩溃,具体取决于编译器的实现?
【解决方案2】:

如果通过指向基础子对象的指针删除对象并且子对象的类没有虚拟析构函数,则行为未定义。

另一方面,如果它确实有一个虚拟析构函数,那么虚拟分派机制负责为正确的地址(即,为完整的、最衍生的对象)释放正确的内存量。

您可以通过将dynamic_cast<void*> 应用于任何适当的基本子对象指针来自己发现最派生对象的地址。 (另见this question。)

【讨论】:

  • 这对于未定义的行为是正确的,但对于内存释放来说是不正确的。即使没有虚拟析构函数,也将始终释放等于真实对象大小的字节数。但是,当然不会调用适当的析构函数。
  • @SergeyA:没有虚拟析构函数,你甚至找不到正确的 address 来释放,更不用说大小了......
  • Kerrek,为什么?您释放整个内存块,释放例程知道大小(通常因为它是块的前缀),然后从指针中给出的地址开始。
  • @SergeyA 所以如果我理解正确的话,指针指向什么并不重要,因为引用的内存块隐含地包含有关它的大小的信息?
  • @SergeyA:您只能通过分配函数返回的指针释放内存。一些随机子对象的地址通常不是这样的指针。大小也很重要,因为它也可以传递给释放函数(作为第二个参数),请参阅 [expr.delete]/(10.1)。
【解决方案3】:

要释放分配的内存,程序必须知道要释放的内存大小,对吧?

如果您考虑 C 库 mallocfree,您会发现调用 free 时无需指定要释放的内存量,即使在这种情况下提供了 free void* 所以无法推断。取而代之的是,分配库通常要么记录,要么可以推断出所提供的内存足够多,这样仅指针就足以进行释放。

这对于 C++ 释放例程仍然适用:如果基类提供自己的 static void operator delete(void*, std::size_t)并且基类析构函数是 virtual,那么它将传递动态的大小类型。默认情况下,取消分配以::operator delete(void*) 结束,不会给出任何大小:分配例程本身必须知道足够的信息才能操作。

分配例程有多种工作方式,包括:

  • 存储分配的大小

  • 从相同大小的块池中分配相似大小的对象,这样任何指向该池的指针都会隐含地与该块大小相关

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-21
    • 2010-10-30
    • 1970-01-01
    • 2012-01-27
    • 1970-01-01
    • 2011-05-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多