Pinpointing the problem
First of all, let us be clear about exactly what the problem is by illustrating the state of the memory after each statement.
int main() {
auto p = (Person*)malloc(sizeof(Person));
// +---+ +-------+
// | p | -> | ~~~~~ |
// +---+ +-------+
p = new(p)Person();
// +---+ +-------+
// | p | -> | name |
// +---+ +-------+
p->name=Test1::MSG1;
// +---+ +-------+ +---...
// | p | -> | name | -> |Something...
// +---+ +-------+ +---...
free(p);
// +---+ +---...
// | p | |Something...
// +---+ +---...
return 0;
}
As you can see, calling free(p)
freed up the memory originally allocated by malloc
, but it didn't free the memory allocated by p->name
when it was assigned to.
This is your leak.
Solving the problem
There are two aspects to having a Person
object on the heap:
- A memory allocation—handled by
malloc
/free
here.
- Initializing and Finalizing that memory—handled by calls to constructors and destructors.
You're lacking the call to the destructor, hence resources held by Person
are leaked. Here it's memory, but if Person
held a lock you could have a forever locked mutex, etc... executing the destructor is therefore necessary.
The C-style approach is to call the destructor yourself:
int main() {
auto p = (Person*)malloc(sizeof(Person));
p = new(p) Person();
p->name = Test1::MSG1;
std::cout << "name: "<< p->name << "\n";
// Problem "fixed".
p->~Person();
free(p);
std::cout << "done" << "\n";
return 0;
}
However this is not idiomatic C++: it's error prone, etc...
The C++ approach is to use RAII to ensure that when p
goes out of scope, all its resources are properly disposed of: the destructor of Person
is executed and the memory allocated for Person
itself is freed.
First of all, we're going to create some helpers. I used the c
namespace since I don't know the name of the C library you use, but I invite you to be more specific:
namespace c {
struct Disposer<T> {
void operator()(T* p) {
p->~T();
free(p);
}
};
template <typename T>
using UniquePointer<T> = std::unique_ptr<T, Disposer<T>>;
template <typename T, typename... Args>
UniquePointer<T> make_unique(T* t, Args&&... args) {
try {
new (t) T(std::forward<Args>(args)...);
} catch(...) {
free(t);
throw;
}
return UniquePointer{t};
}
} // namespace c
And with that, we can improve the original example:
int main() {
auto raw = (Person*) malloc(sizeof(Person));
auto p = c::make_unique(raw);
p->name = Test1::MSG1;
std::cout << "name: "<< p->name << "\n";
// No need to call the destructor or free ourselves, welcome to RAII.
std::cout << "done" << "\n";
return 0;
}
Note: Do not use std::endl
, use '\n'
or "\n"
instead. std::endl
calls .flush()
on top of putting an end of line, which is rarely what you want -- it slows things down.
free
. – Hadikmalloc
doesn't construct objects,free
doesn't destruct objects. – Spielmanalignas
, if ever I saw one. – Dishclothfree
solves the problem. Please write an answer, I will accept it the correct answer. – Inspection#include <string>
and#include <cstdlib>
– Amara<string>
and<cstdlib>
are included by<iostream>
(the standard neither requires nor prevents it). – Coacervatemalloc
andfree
instead ofstd::malloc
andstd::free
. – Camberalignas
, @PaulSanders, given thatstd::malloc()
returns memory suitably aligned so that it may be assigned to a pointer to any type of object with a fundamental alignment requirement (or a null pointer, of course). – Cambervoid *v = malloc(sizeof(Person)); auto p = new(v)Person;
– Dishclothstd::malloc()
is not yet aPerson*
. It's not clear exactly how the library's allocator is used - if allPerson
objects need to be allocated by the library, that would be a case for definingPerson::operator new()
andPerson::operator delete()
to encapsulate the allocation appropriately. – CamberMSG1
is "really big", and it's a compile-time constant, I'd recommend making it astd::string_view
(a view of the raw data stored in the executable's constants store) instead ofstd::string
(which must be allocated and populated from the constant store at runtime). Just#include <string_view>
, and if you useusing namespace std::literals;
, initialization simplifies tostatic constexpr auto MSG1="Something really big message"sv;
(notesv
suffix). Not a huge difference, but you'll halve memory overhead ofMSG1
and speed startup. – Wallioperator new()
instead of malloc. I don't really understand how you end up in a situation where you are required to use malloc while std::string isn't. (This is not a critique, but genuine curiosity.) – Whiffletreemalloc
in my c++ code. I call a C function (from a lib) which internally uses malloc to provide some functionality. This C lib provides its custom freeing method when I wanted to release this memory. However when I free it that way, it was giving me error. But as other's have pointed out, that if I useplacement new
operator than I do need to call destructor manually before releasing memory. Which on doing so solves the issue. – Inspection