I am aware of the differences between free and delete in C++. But one thing I never understood is why in C malloc/free can allocate de-allocate both single 'objects' and arrays but in C++ we need to use the correct new/delete vs new[]/delete[] pair.
Searching on Stackoverflow, it seems that in C++, new[] allocates extra memory to hold the size of the allocated array and new only allocates the memory to the object itself. And because of that, you should be aware of this extra overhead.
If the previous paragraph is indeed the case, then how malloc/free handles this overhead? Or they just accept this overhead? And if it is tolerable in C, why not in C++?
On the other hand, in case it's not because of memory overhead, but because of calling constructors and destructors, couldn't the compiler be smart enough to generate the appropriate code under the hood and let the programmer just write new/delete for both single objects and arrays of objects?
I am writing a compiler for a toy language whose semantics is similar to C++ and it seems that it is possible to let the compiler decides how to allocate and de-allocate only using new/delete, but as C++ uses new/delete and new[]/delete[], maybe there's a catch that I am not seeing right now. Maybe something related to polymorphism and virtual tables?
If you're curious, my naive idea is to simple allocate an integer together with the object/array where this integer is the size of the array or simple 1 in case of being an object. Then, when calling delete, it checks the value of the integer, if it is 1, it calls the destructor. If it greater than 1, then it iterates the array calling the destructor to each object in the array. As I said, it seems to work and would let the programmer just write new/delete instead of new/delete vs new[]/delete. But then again, maybe there's a catch that I am not seeing.
Edited part:
After some answers, I decided to try to provide some pseudo-code and a better background.
In C language, memory allocations are usually made with malloc() and de-allocations with free(). Either if you are allocating a single POD, a single struct or an array, malloc() fits all these cases. There is no need for different versions of malloc() if you are allocating a single struct vs a malloc_array() version if you are allocating an array. At least at the public API level. In other words, it seems it doesn't matter if you are allocating a few bytes or many bytes, there will be no overhead for bookkeeping the allocation size information.
As many of you are aware, including myself, new and delete do more than just allocate and de-allocate memory. New allocate memory and call the constructor and delete calls the destructor and then de-allocate memory. But in C++, you need to be aware if you are allocating just a single object or an array of objects. In case you are allocating an array, you need to use the new[]/delete[] pair.
In C, if you implement a binary tree, nodes will be allocated with malloc and de-allocated with free and in C++ with new and delete. But if you are implementing something like the vector class in C++, in C you still would use malloc/free, but now in C++ you would need to use new[]/delete[] (considering a sane implementation without too much black magic).
Consider the following pseudo-code that is executed by the compiler. In this pseudo-code, the delete function somehow gets access to the malloc internals and knows how many bytes there are, which in turn can be easily used to calculate how many objects there are. As this delete implementation is using malloc internals to know how much memory is allocated, in theory there should be no overhead of bookkeeping.
// ClassType is a meta type only know by the compiler
// it stores a class info such as name, size, constructors and so on
void *new(ClassType c) {
// allocates memory with malloc. Malloc() do the storage bookkeeping
// note that somehow malloc is allocating just a single object
c *ptr = malloc(sizeof(c));
// now, call the constructor of the requested class
c.constructor(ptr);
// return the new object
return ptr;
}
void *new(ClassType c, size_t n) {
c *ptr = malloc(sizeof(c) * n);
// iterate over the array and construct each object
for (i = 0; i < n; ++i) {
c.constructor(ptr[i]);
}
return ptr;
}
// this delete version seems to be able to de-allocate both single
// objects and arrays of objects with no overhead of bookkeeping because
// the bookkeeping is made by malloc/free. So I would need
// just a new/delete pair instead of new/delete vs new[]/delete[]
// Why C++ doesn't use something like my proposed implementation?
// What low-level details prohibits this implementation from working?
void delete(ClassType c, void *ptr) {
// get raw information of how many bytes are used by ptr;
size_t n = malloc_internals_get_size(ptr);
// convert the number of bytes to number of objects in the array
n = c.bytesToClassSize(n);
c* castedPointer = (c*) ptr;
// calls the destructor
for (i = 0; i < n; ++i) {
c.destructor(castedPointer[i]);
}
// free memory chunk
free(ptr);
}
malloc
declaration, it accepts size in bytes and returns an unitialized chunk of memory of typevoid *
, whichfree
later releases. On the contrary,new
constructs obects anddelete
destructs them so it needs to know it should act on each element of the array. It could be made array-aware, but they chose such a boilerplate approach, I have no idea why. – Fuguenew
could be doing a lot of things. So couldmalloc
. Or whatever is beneathmalloc
. You could ask for 2 bytes and get a 4K allocation if that what the source of memory has available to give. – Tmesisdelete[]
. To implementdelete
it is not necessary to have that integer at all, hence lesser overhead – Pteromalloc()
/free()
has no concept of arrays at all.malloc()
just allocates a single chunk of memory, andfree()
just deallocates it. They don't care what is stored inside that memory, that is your responsible to handle.new
/new[]
, on the other hand, do care what is in the memory, so they need to differentiate. – Justenmalloc()
/free()
is implementation-defined. What you describe is just 1 possible implementation. The standard doesn't dictate any implementation. And anyway,malloc()
andnew[]
are semantically different, asnew[]
/delete[]
have to loop through array elements individually for non-trivial types, whilemalloc()
/free()
don't. So the element count has to be stored somewhere no matter what, whereas the byte count can be optimized. And FYI, many implementations ofnew
/delete
andnew[]
/delete[]
usemalloc()
/free()
internally – Justen