I know how they are different syntactically, and that C++ uses new, and C uses malloc. But how do they work, in a high-level explanation?
See What is the difference between new/delete and malloc/free?
I'm just going to direct you to this answer: What is the difference between new/delete and malloc/free? . Martin provided an excellent overview. Quick overview on how they work (without diving into how you could overload them as member functions):
Sample:
new (a, b, c) TypeId;
// the function called by the compiler has to have the following signature:
operator new(std::size_t size, TypeOfA a, TypeOfB b, TypeOf C c);
new_handler
, and hope it makes place. If there still is not enough place, new has to throw std::bad_alloc
or derived from it. An allocator that has throw()
(no-throw guarantee), it shall return a null-pointer in that case. There are a few special allocation functions given special names:
no-throw
new. That takes a nothrow_t
as second argument. A new-expression of the form like the following will call an allocation function taking only std::size_t and nothrow_t:Example:
new (std::nothrow) TypeId;
placement new
. That takes a void* pointer as first argument, and instead of returning a newly allocated memory address, it returns that argument. It is used to create an object at a given address. Standard containers use that to preallocate space, but only create objects when needed, later.Code:
// the following function is defined implicitly in the standard library
void * operator(std::size_t size, void * ptr) throw() {
return ptr;
}
If the allocation function returns storage, and the the constructor of the object created by the runtime throws, then the operator delete is called automatically. In case a form of new was used that takes additional parameters, like
new (a, b, c) TypeId;
Then the operator delete that takes those parameters is called. That operator delete version is only called if the deletion is done because the constructor of the object did throw. If you call delete yourself, then the compiler will use the normal operator delete function taking only a void*
pointer:
int * a = new int;
=> void * operator new(std::size_t size) throw(std::bad_alloc);
delete a;
=> void operator delete(void * ptr) throw();
TypeWhosCtorThrows * a = new ("argument") TypeWhosCtorThrows;
=> void * operator new(std::size_t size, char const* arg1) throw(std::bad_alloc);
=> void operator delete(void * ptr, char const* arg1) throw();
TypeWhosCtorDoesntThrow * a = new ("argument") TypeWhosCtorDoesntThrow;
=> void * operator new(std::size_t size, char const* arg1) throw(std::bad_alloc);
delete a;
=> void operator delete(void * ptr) throw();
If you do
new (possible_arguments) TypeId[N];
The compiler is using the operator new[]
functions instead of plain operator new
. The operator can be passed a first argument not exactly sizeof(TypeId)*N
: The compiler could add some space to store the number of objects created (necassary to be able to call destructors). The Standard puts it this way:
new T[5]
results in a call of operator new[](sizeof(T)*5+x)
, andnew(2,f) T[5]
results in a call of operator new[](sizeof(T)*5+y,2,f)
.What new
does differently form malloc
is the following:
operator new
. This behaviour can be adapted by overloading this operator, either for all types, or just for your class.So all in all, new
is highly customizable and also does initialization work besides memory allocation. These are the two big differences.
Although malloc
/free
and new
/delete
have different behaviors, they both do the same thing at a low level: manage dynamically allocated memory. I'm assuming this is what you're really asking about. On my system, new
actually calls malloc
internally to perform its allocation, so I'll just talk about malloc
.
The actual implementation of malloc
and free
can vary a lot, since there are many ways to implement memory allocation. Some approaches get better performance, some waste less memory, others are better for debugging. Garbage collected languages may also have completely different ways of doing allocation, but your question was about C/C++.
In general, blocks are allocated from the heap, a large area of memory in your program's address space. The library manages the heap for you, usually using system calls like sbrk
or mmap
. One approach to allocating blocks from the heap is to maintain a list of free and allocated blocks which stores block sizes and locations. Initially, the list might contain one big block for the whole heap. When a new block is requested, the allocator will select a free block from the list. If the block is too large, it can be split into two blocks (one of the requested size, the other of whatever size is left). When an allocated block is freed, it can be merged with adjacent free blocks, since having one big free block is more useful than several small free blocks. The actual list of blocks can be stored as separate data structures or embedded into the heap.
There are many variations. You might want to keep separate lists of free and allocated blocks. You might get better performance if you have separate areas of the heap for blocks of common sizes or separate lists for those sizes. For instance, when you allocated a 16-byte block, the allocator might have a special list of 16-byte blocks so allocation can be O(1). It may also be advantageous to only deal with block sizes that are powers of 2 (anything else gets rounded up). For instance, the Buddy allocator works this way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With