Freeing memory without destructing

Say we have this crude memory pool that is on the heap:

 
char* mpool = (char*)malloc(sizeof(char) * 4096);


And we used placement new to construct a couple objects in place:

1
2
3
4
5
char* next = mpool;
void* fooptr = new (next) FOO();
next += sizeof(FOO);
void* bazptr = new (next) Baz();
next += sizeof(Baz);


And at the end, let's say the memory is freed without having called the object destructors:

 
free(mpool);


What exactly happens? I haven't found any page that states what happens in this situation. Does it cause undefined behavior?
The destructors are not called, so I would fear resource leak.

If you don't know what a code does, then don't write such a code.
If you receive such a code, change it to have a valid code that you do know what it does.
So how would you ensure that the destrcutors are always called? The only thing I can think of is a Chunk class template (that's like a pointer wrapper) that stores a pointer to the memory pool it belongs to. And upon destruction of a Chunk object, it can call a member function of a MemoryPool object to call the appropiate destructor.

Would this be too complex? I don't want to depend on the user to manually clean up resources.

I was going to try writing a really basic memory pool class just for practice, but I didn't realize it would already be tricky.
> Does it cause undefined behavior?

This will cause undefined behaviour if bazptr is not aligned on std::alignment_of<BAZ>::value

This may (probably will) result in resource leaks if std::is_trivially_destructible<FOO>::value is false
or std::is_trivially_destructible<BAR>::value is false



> So how would you ensure that the destrcutors are always called?

By using smart pointers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <memory>
#include <iostream>
#include <type_traits>
#include <cstdlib>
#include <new>

template < typename T > struct destroy
{
  void operator() ( T* p ) const { p->~T() ; }
};


template < typename T >
using placement_ptr = std::unique_ptr< T, destroy<T> > ;

constexpr std::size_t max( std::size_t a, std::size_t b ) { return a<b ? b : a ; }

struct foo { /* ... */ ~foo() { std::cout << "foo::~foo()\n" ; } };
struct baz { /* ... */ ~baz() { std::cout << "baz::~baz()\n" ; } };

using fooptr_t = placement_ptr<foo> ;
using bazptr_t = placement_ptr<baz> ;

int main()
{
    constexpr std::size_t sz = max( sizeof(foo), sizeof(baz) ) ;
    constexpr std::size_t align = max( std::alignment_of<foo>::value,
                                       std::alignment_of<baz>::value ) ;
    using type = std::aligned_storage< sz, align >::type ;

    char* mpool = static_cast<char*>( std::malloc( sizeof(type) * 32 ) ) ;

    {


        fooptr_t fooptr( ::new (mpool) foo( /* ... */ ) ) ;
        bazptr_t bazptr( ::new ( mpool + sizeof(type) ) baz( /* ... */ ) ) ;

        // ...

    }

    std::free(mpool) ;
}




> I was going to try writing a really basic memory pool class just for practice,
> but I didn't realize it would already be tricky.

It is not tricky if you do not try to keep objects of different types in the same pool.
You may want to use boost::object_pool<> and boost::pool_allocator<> as reference implementations.
http://www.boost.org/doc/libs/1_54_0/libs/pool/doc/html/index.html
Last edited on
Oh, I'm still new to placement new, so I didn't know you could use it with smart pointers. You have enlightened me very much. Now I understand better what those extra parameters in the smart pointer constructors are for.
I've never had to deal with alignment, so this will be an extra challenge for me.

Thanks, ne555 and JLBorges for your insight!
Topic archived. No new replies allowed.