Suppose this is a struct/class in c++
struct MyClass{
list<int> ls;
};
Now maybe due to some reason, it is not possible to initialize pointer of 'MyClass', and the memory is being allocated manually
MyClass *pointer = calloc(1, sizeof(MyClass));
pointer->ls.push_back(123); // <----- this line causes core dump
Although it works fine with pure C++ method like this:-
MyClass *pointer = new MyClass;
pointer->ls.push_back(123);// All good, no problem
So is there any function available in std::list to resolve this issue?
I tried doing the same with std::vector, by there was no problem in that when I used calloc()
struct MyClass{
vector<int> ls;
};
MyClass *pointer = calloc(1, sizeof(MyClass));//using calloc
pointer->ls.push_back(123); // All Good
But on doing this using malloc:-
struct MyClass{
vector<int> ls;
};
MyClass *pointer = malloc(1, sizeof(MyClass));//using malloc
pointer->ls.push_back(123); // Core dump :-(
C++ differentiates between memory and objects in that memory. You are allocating memory with malloc
or calloc
, but you are not creating any object in the memory you allocated.
You can manually create an object in allocated storage using the placement-new expression:
new(pointer) MyClass /*initializer*/;
This creates a new MyClass
object at the storage location that pointer
points to. /*initializer*/
is the optional initializer for the object and has the same meaning as in a variable declaration
MyClass my_class /*initializer*/;
You may need to #include<new>
to use the placement-new form I used above and you need to make sure that the allocated memory block is large enough and sufficiently aligned for the MyClass
object type.
Using a pointer as if it points to an object even though none was created at the storage location causes undefined behavior, which you are observing. Note that this is true for any (even fundamental) types technically. Probably that will change in the future to allow at least trivial types (or some similar category) to be created implicitly when used, but for non-trivial class types such as the standard containers, this certainly will not change.
Also note that malloc
and calloc
return void*
, which in C++ cannot be implicitly cast to a different pointer type. You need to cast it explicitly:
MyClass *pointer = static_cast<MyClass*>(malloc(sizeof(MyClass)));
or rather than doing that, save the pointer as void*
without any cast to hint at you that it is not actually pointing to an object, but just memory.
You can still pass that void*
pointer to the placement-new and the placement-new will actually return you a pointer of the correct type pointing to the new object (there are some specific cases where you are actually required to use this pointer):
void *mem_ptr = malloc(sizeof(MyClass));
auto obj_ptr = new(mem_ptr) MyClass /*initializer*/;
You should also avoid using malloc
and calloc
and the like. C++ has its own memory allocation function, called (confusingly) operator new
. It is used like malloc
(initializing the memory to zero like calloc
does is not useful, because the initializer in the placement-new can and/or will do that):
void* pointer = operator new(sizeof(MyClass));
and its free
analogue is:
operator delete(pointer);
It still only allocates memory and does not create object as the placement new does.
The non-placement new expression new MyClass;
allocates memory (by call to operator new
) and creates an object as if by the placement-new form mentioned above.
You do not need to bother with all of this though, because you can just use a smart pointer and initialize it later, if you need that:
std::unique_ptr<MyClass> ptr; // No memory allocated or object created
ptr = std::make_unique<MyClass>(/*constructor arguments*/); // Allocates memory and creates object
// Object is automatically destroyed and memory freed once no `std::unique_ptr` references the object anymore.
You should not use raw pointers returned from new
, malloc
or the like as owning pointers in the first place. std::unique_ptr
has the correct ownership semantics by default and requires no further actions by you.