Thomas L Holaday
Thomas L Holaday

Reputation: 13814

Are there any conformant C++ compilers that will leak memory for PODS derived from PODS?

Given:

#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;
}

Summary:

If compiled with a conformant C++ compiler, will the system show memory leaks from either stress or extreme_stress? If so, what is an example of a compiler in general release which does in fact produce a leaky binary?

Upvotes: 3

Views: 218

Answers (3)

Stack Overflow is garbage
Stack Overflow is garbage

Reputation: 248149

A conformant C++ compiler can do anything it likes with that code. It could format your harddrive, or email your collection of porn to your grandma. It may even delete the objects correctly.

Your program is not legal C++.

Your question can be simplified like this:

Given the following two classes:

struct Base{
  int i;
};

struct Derived : public Base{
  int j;
};

will any conformant C++ compiler leak memory in the following cases:

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

Correct? The number of allocations, or the size of each class isn't really relevant to the question.

If so, the answer is simple. In both cases, the result is undefined behavior. According to 5.3.5:3, if the static type of the object being deleted is different from the dynamic type, the static type shall be a base class of the operand's dynamic type and the static type shall have a virtual destructor.

In the first example, the static type (Base) is a base class of the dynamic type (Derived), but it doesn't have a virtual destructor.

In the second case, neither requirement is fulfilled.

So is there a compiler where this won't still appear to work? I don't know of one. The compilers I know of will release the memory as long as the pointer passed to delete points to the same address as the one that was returned from new. And that will be the case in both cases here.

However, you're asking the wrong question. It is not "will it leak memory" that is important, but "will it do anything bad?" It is undefined behavior, so it might do a hell of a lot more than leak memory. An obvious thing it could do is just do a quick type check, and then terminate your program if the types don't match up. Or it could corrupt the heap. There are plenty of things that a conformant compiler could do. Memory leaks are just one option.

Finally, it's worth pointing out that the derived class is not a POD. A POD, by definition, may not derive from another class.

Upvotes: 3

John Kugelman
John Kugelman

Reputation: 361909

If you delete via an int pointer, the compiler will not call the destructor for the object being deleted. Since these are PODs with no destructor, that's not a factor.

The other issue is whether the memory will be freed properly. I can't imagine an implementation where it wouldn't since the memory allocation functions underlying new and delete will just be like malloc and free, i.e. type-independent functions that operate on void * pointers.

Pedantically speaking you are invoking undefined behavior, but practically speaking I will eat my hat* if there's a compiler that doesn't handle this sanely.

* I do not have a hat.

Upvotes: 2

Pavel Minaev
Pavel Minaev

Reputation: 101605

Very unlikely. Most (all?) C++ compilers simply make the default operator new and operator delete wrap malloc and free, or other memory allocation primitives that normally don't know anything about objects and types, and allocate/release raw chunks of memory (and thus have to store the size within to know how much to release). Naturally, for overloaded operator new & delete, all bets are off.

stress is even more pointless, as any memory allocator worth its salt will simply keep reusing the same memory block again and again, since it's so conveniently sized to match the object being allocated.

Of course, in theory, a conformant C++ compiler might leak on your delete (int*). Or it might format your hard drive. Or it might hack Pentagon and launch an all-out nuclear missile strike. Because that's what U.B. means in ISO C++ - absolutely anything can happen, including nothing - and what you're doing there is definitely U.B.

Upvotes: 3

Related Questions