是否有任何一致的C ++编译器会泄漏PODS派生的PODS的内存?

时间:2009-07-10 04:51:22

标签: c++ memory-management

假设:

#include <iostream>
using namespace std;

struct Concrete {
  char name[20];
  char quest[20];
  char favorite_color[13];
};

struct Concrete_with_knobs : public Concrete {
  int knobs[100000];
};

Concrete * cradle() {
    return new Concrete_with_knobs;
}

void grave(Concrete *p) {
    delete p;
}

void tomb_of_the_unknown_allocation(void *pv) {
    delete static_cast<int *>(pv);
}

void stress() {
    for (int i = 0; i < 1000000; ++i) {
        Concrete *p = cradle();
        grave(p);
    }
}

void extreme_stress() {
    for (int i = 0; i < 1000000; ++i) {
        Concrete *p = cradle();
        void *pv = p;
        tomb_of_the_unknown_allocation(pv);
    }
}

int main(int, char **) {
    cout << "sizeof(Concrete_with_knobs): " << sizeof(Concrete_with_knobs) << endl;
    cout << "Check your memory." << endl;
    char c;
    cin >> c;
    cout << "Stress." << endl;
    stress();
    cout << "Check your memory." << endl;
    cin >> c;
    cout << "Extreme stress." << endl;
    extreme_stress();
    cout << "Check your memory." << endl;
    cin >> c;
    return 0;
}

摘要:

  • 派生类(Concrete_with_knobs)比其基类(Concrete)大400K。
  • cradle 在堆上创建一个Concrete_with_knobs对象,并将其地址作为Concrete指针返回。
  • grave 将地址作为具体指针,并执行删除操作。
  • tomb_of_the_unknown_allocation 将地址作为void *,使用static_cast将其解释为int的地址,并执行删除操作。
  • 压力打电话给摇篮并坟墓一百万次。
  • extreme_stress 调用 cradle tomb_of_the_unknown_allocation 一百万次。

如果使用符合条件的C ++编译器编译,系统是否会显示来自stress或extreme_stress的内存泄漏?如果是这样,一般版本中的编译器的例子是什么,实际上会产生泄漏的二进制文件?

3 个答案:

答案 0 :(得分:3)

非常不可能。大多数(所有?)C ++编译器只是使默认的operator newoperator delete包裹mallocfree,或其他通常不了解对象的内存分配原语和类型,并分配/释放原始内存块(因此必须存储大小以了解要释放多少)。当然,对于过载的operator new&amp; delete,所有赌注都已关闭。

stress更加毫无意义,因为任何值得盐的内存分配器都会一次又一次地重复使用相同的内存块,因为它的大小非常方便,可以匹配正在分配的对象。

当然,理论上,符合条件的C ++编译器可能会泄漏delete (int*)。或者它可能会格式化您的硬盘。或者它可能会破坏五角大楼并发动全面的核导弹袭击。因为那就是U.B.在ISO C ++中意味着 - 绝对可能发生任何事情,包括什么都没有 - 而你在那里做的肯定是U.B。

答案 1 :(得分:3)

符合条件的C ++编译器可以使用该代码执行它喜欢的任何。它可以格式化您的硬盘,或通过电子邮件将您的色情集合发送给您的奶奶。它甚至可以正确删除对象。

您的程序不是合法的C ++。

您的问题可以简化为:

给出以下两个类:

struct Base{
  int i;
};

struct Derived : public Base{
  int j;
};

在以下情况下,任何符合要求的C ++编译器都会泄漏内存:

// 1
Base* b1 = new Derived();
delete b1;
// 2
Base* b2 = new Derived();
void* vp = b2;
delete static_cast<int*>(vp);

正确?分配数量或每个类别的大小与问题无关。

如果是这样,答案很简单。在这两种情况下,结果都是未定义的行为。根据5.3.5:3,如果被删除对象的静态类型与动态类型不同,则静态类型应为操作数动态类型的基类,静态类型应具有虚拟析构函数。

在第一个示例中,静态类型(Base)是动态类型(Derived)的基类,但它没有虚拟析构函数。

在第二种情况下,既没有满足要求。

那么有一个编译器,它似乎仍然无法正常工作?我不知道一个。 只要传递给delete的指针指向与new返回的地址相同的地址,我知道的编译器就会释放内存。这两种情况都是如此。

但是,你问的是错误的问题。重要的不是“它会泄漏记忆”,而是“它会做什么坏事吗?”它是未定义的行为,因此它可能会比泄漏内存做得更多。它可以做的一件显而易见的事情就是快速进行类型检查,然后在类型不匹配的情况下终止程序。或者它可能会破坏堆。一致的编译器可以做很多事情。内存泄漏只是一种选择。

最后,值得指出派生类不是POD。根据定义,POD可能不是来自另一个类。

答案 2 :(得分:2)

如果通过int指针删除,编译器将不会为要删除的对象调用析构函数。由于这些是没有析构函数的POD,所以这不是一个因素。

另一个问题是内存是否可以正常释放。由于newdelete下面的内存分配函数就像mallocfree,即类型无关的函数,我无法想象它不会实现的实现对void *指针进行操作。

迂腐地说,你正在调用未定义的行为,但实际上我会吃掉我的帽子*如果有一个编译器无法妥善处理这个问题。

*我没有帽子。