advanced programming & c++ language · advanced programming & c++ language heap management...
TRANSCRIPT
Dr. Miri (Kopel) Ben-NissanAriel University2018
Advanced Programming & C++ Language
Heap Management in C & C++
~7~
© Miri Ben-Nissan
The Traditional Heap Manager 2
The standard C library routines for allocating and freeing memory are malloc() and free().
C++ uses the “new” and “delete” operators.
void* malloc ( size_t size );
void free ( void *memblock );
void* operator new (size_t);
void operator delete (void*);
void* operator new[](size_t);
void operator delete[](void*);
© Miri Ben-Nissan
The Traditional Heap Manager (Cont…)
3
Main problem with those interfaces: they assumes the programmer never makes a mistake in calling the memory management functions. #include <stdlib.h>
int main(void)
{
void *pMem=malloc(100);
free(pMem);
free(pMem);
return 0;
}
<- behavior undefined!
© Miri Ben-Nissan
The Traditional Heap Manager (Cont…)
4
Besides the interface, there is also general problems with dynamic memory allocations:
Allocation and de-allocation are non-deterministic with respect to time.
De-allocation:Explicit by the programmer can cause memory leaks if not
handled correctly. Implicit by garbage collector, nondeterministic.
Fragmentation problem.
© Miri Ben-Nissan
malloc5
void *malloc(long numbytes): This allocates numbytes of memory and returns a pointer to the first byte.Returns NULL if failed.Guaranties well fragmentation.
void free(void *firstbyte): Given a pointer that has been returned by a previous malloc, this gives the space that was allocated back to the process's "free space."
© Miri Ben-Nissan
malloc (cont…)
6
You can imagine the heap as a vector of bytes (characters), starting at address managed_memory_start, and last_valid_address as a pointer to the first available byte in the heap.
int has_initialized = 0;
void *managed_memory_start; //start of heap
void *last_valid_address; //end of heap
void *last_allocated= managed_memory_start;
//no allocations yet.
Malloc (Cont…)
© Miri Ben-Nissan
malloc (cont…)
7
A trivial implementation of malloc in C is:
void* malloc ( int size )
{
void* loc = last_allocated;
last_allocated += size;
return loc;
};
Malloc (Cont…)
© Miri Ben-Nissan
malloc (cont…)
8
If you always create dynamic structures but never delete them, you will eventually run out of heap space.
In that case, malloc will request the operating system for more heap. This is very expensive, because it may require to
move the stack data to higher memory locations.
Malloc (Cont…)
© Miri Ben-Nissan
malloc (cont…)
9
Alternatively, you can recycle the dynamically allocated data that you don't use.
This is done using free in C or delete in C++.
How we will know how many bytes to move the pointer back?
What if it is in the middle of the heap?
© Miri Ben-Nissan
malloc (cont…)
10
We will add an header to each allocated block in the heap:
struct mem_control_block
{
int is_available;
int size;
};
© Miri Ben-Nissan
malloc (cont…)
11
As said previously, the heap is a continues (in terms of virtual address) space of memory with three bounds: Starting point. Maximum limit. End point (break).
The break marks the end of the mapped memory spaces, that is, the part of the virtual address space that has correspondence into real memory.
© Miri Ben-Nissan
12
In the Unix operating system, we find the following two system calls that enables us to control the break border:
brk places the break at the given address addr and returns 0 if successful, -1 otherwise.
sbrk moves the break by the given increment (in bytes). Depending on system implementation, it returns the previous or the new break address. On failure, it returns (void*)-1. On some systems, sbrk accepts negative values (in order to free some mapped memory).
malloc (cont…)
© Miri Ben-Nissan
malloc (cont…)
13 when increment is null (i.e. sbrk(0)), the returned value is the
actual break address. sbrk is thus used to retrieve the beginning of the heap which is
the initial position of the break.
Accessing addresses above the break should trigger a bus error. The remaining space between the break and the maximum limit of the heap is not associated to physical memory by the virtual memory manager of the system.
© Miri Ben-Nissan
14
#include <unistd.h> /* Include the sbrk function */
int has_initialized = 0;
void *managed_memory_start;
void *last_valid_address;
void malloc_init()
{
/* grab the last valid address from the OS */
last_valid_address = sbrk(0);
/* we assume that managed memory size at the beginning
* is empty, so just set the beginning to be
* last_valid_address */
managed_memory_start = last_valid_address;
has_initialized = 1;
}
malloc (cont…)
© Miri Ben-Nissan
15
struct mem_control_block {
int is_available;
int size;
};
void free(void *firstbyte)
{
struct mem_control_block *mcb;
mcb = firstbyte - sizeof(struct mem_control_block);
mcb->is_available = 1;
return;
}
malloc (cont…)
© Miri Ben-Nissan
16
void *malloc(long numbytes)
{
/* Holds where we are looking in memory */
void *current_location;
/* This is the same as current_location, but cast to a
memory_control_block */
struct mem_control_block *current_location_mcb;
void *memory_location;
if(! has_initialized) {
malloc_init();
}
malloc (cont…)
© Miri Ben-Nissan
17
numbytes = numbytes + sizeof(struct mem_control_block);
/* Set memory_location to 0 until we find a suitable location */
memory_location = 0;
/* Begin searching at the start of managed memory */
current_location = managed_memory_start;
TH
E H
EA
P
numbytesheadermanaged_memory_start
current_location
last_valid_address
malloc (cont…)malloc (cont…)
© Miri Ben-Nissan
18
/* Keep going until we have searched all allocated space */
while(current_location != last_valid_address)
{
current_location_mcb =
(struct mem_control_block *)current_location;
if(current_location_mcb->is_available){
if(current_location_mcb->size >= numbytes){
current_location_mcb->is_available = 0;
memory_location = current_location;
break;
}
}
/* If we made it here, it's because the current memory
* block not suitable, move to the next one */
current_location = current_location +
current_location_mcb->size;
}
malloc (cont…)
© Miri Ben-Nissan
19
/* If we still don't have a valid location, we'll have to ask the
operating system for more memory */
if(! memory_location)
{
/* Move the program break numbytes further */
if (sbrk(numbytes) == (void*)(-1))
return 0; /*unable to resize heap size.*/
/* The new memory will be where the last valid address left off*/
memory_location = last_valid_address;
/* We'll move the last valid address forward numbytes */
last_valid_address = last_valid_address + numbytes;
/* We need to initialize the mem_control_block */
current_location_mcb =
(struct mem_control_block *)memory_location;
current_location_mcb->is_available = 0;
current_location_mcb->size = numbytes;
}
malloc (cont…)malloc (cont…)
© Miri Ben-Nissan
20
/* Move the pointer past the mem_control_block */
memory_location =
memory_location + sizeof(struct mem_control_block);
/* Return the pointer */
return memory_location;
}
Taken from:
http://www.ibm.com/developerworks/linux/library/l-memory/
malloc (cont…)
© Miri Ben-Nissan
Exercise21
Write an aligned malloc & free function, which takes two parameters: The number of bytes to allocate. The aligned byte (which is always power of 2).
Example: align_malloc (1000,128);
will allocate the memory and return a memory address which is a multiplication of 128.
aligned_free();
will free memory allocated by align_malloc.
malloc (cont…)
© Miri Ben-Nissan
In their turn, malloc and newmake calls to theoperating system kernel requesting memory, whilefree and deletemake requests to release memory. This means that the operating system has to switch
between user-space code and kernel code every
time a request for memory is made .smargorP
gnikamdetaepersllacot malloc or new eventuallyrun slowly because of the repeated contextswitching .
© Miri Ben-Nissan
Allocation Policies
23
Allocation Policy = The concrete policy used by an allocator (memory manager) for choosing a free block to satisfy an allocation request.
Two methods: Fixed size blocks. Variable size blocks.
Most popular policies (for both methods): First Fit Best Fit Next Fit
© Miri Ben-Nissan
24
Notice: malloc allocates “pages” (or “chunks”) of fixed size. Than this chunk is split: part of it goes to the user, and the rest goes to the free-blocks list.
Allocation Policies (Cont…)
© Miri Ben-Nissan
Allocation Policies (Cont…)
Best-fit: choose smallest hole that fits creates small holes!
First-fit: choose first hole from beginning that fits
generally superior
Next-fit: variation: first hole from last placement that fits.
(also worst fit)
© Miri Ben-Nissan
Allocation Policies (Cont…)
26
First fit simply searches the free list from the beginning, and uses the first free block large enoughto satisfy the request. If the block is larger than necessary, it is either used as is (internal
fragmentation) or split and the remainder is put on the free list.
Easy to implement Memory overhead is small.
Internal fragmentation. Lots of small blocks at the beginning
© Miri Ben-Nissan
Allocation Policies (Cont…)
27
Best Fit always allocates from the smallest suitable free block. In theory, best fit may exhibit bad fragmentation, but
in practice this is not commonly observed.
Minimizes the amount of wasted space
Sequential best-fit is inefficient
© Miri Ben-Nissan
Allocation Policies (Cont…)
28
Next Fit is a variant of the first fit allocation mechanism that uses a roving pointer on a circular free block chain. Each allocation begins looking where the previous one
finished.
No accumulation of small blocks, which would have to be examined on every allocation.
Generally increases fragmentation, like first-fit. Tendency to spread related objects out in memory. Gives quite poor locality for the allocator (as the roving
pointer rotates around memory, the free blocks touched are those least-recently used).
© Miri Ben-Nissan
Allocation Policies (Cont…)
29
All algorithms can be improved by keeping the information about the free blocks in the heap.
There are many techniques for that, like: Free-page bitmap. Free list.And many more…
Some of the techniques allocates fixed-size blocks and other a variable-sized blocks.
© Miri Ben-Nissan
Allocation Policies (Cont…)
30
Free-page bitmap. Every bit in the bitmap represents one 4096 byte (4KB) block in the heap. If the bit is 0, the block has been allocated. If the bit is 1, then it is free.
The free page bitmap is 1024 bits wide: 4MB = 4096KB => 4096KB/4096B = 1024.
Assume a 4MB
heap.
Each allocated
block is 4KB.
allocation: O(N)
free: O(1)
© Miri Ben-Nissan
Allocation Policies (Cont…)
31
Free list is a linked list of freeblock structures.
struct freeblock
{
struct freeblock *next;
char garbage[4092];
};
In the data portion of the processes, we
store a pointer which points to the first
freeblock structure.Internal fragmentation !
© Miri Ben-Nissan
Allocation Policies (Cont…)
32
Variable Size Allocations allows the allocation of memory of variable sizes.
Instead of having several smaller blocks to allocate, we start with one big block of memory and split it as necessary to make smaller blocks.
struct freeblock
{
struct freeblock *next;
size_t size;
};
© Miri Ben-Nissan
Segregated free lists uses a set of lists of free blocks. Each list holds blocks of a given size
A freed block is pushed onto the relevant list When a block is needed, the relevant list is used
Allocation Policies (Cont…)
© Miri Ben-Nissan
Block Headers Policies34
There are two techniques used to handle the headers of the allocated blocks:
Over-allocate the array and put n just to the left of the beginning of the block.
Use an associative array with p as the key and n as the value.
© Miri Ben-Nissan
Block Headers Policies (Cont…)
35
Using "over-allocation" (header) to keep the size of the block size.
Each block, allocated or freed, contains a header.
Usually the heap simply consists of two lists of cells; one is the list of allocated cells and the other the list of free cells.
© Miri Ben-Nissan
36
Block Headers Policies (Cont…)
© Miri Ben-Nissan
37
Using an "associative array" to remember the size of the block.
An associative array, like a STL map data structure, is used to map a pointer of a block to its size.
The header is kept outside the area of allocated blocks, thus the block size is only the requested size sent by the user to malloc.
Block Headers Policies (Cont…)
© Miri Ben-Nissan
Memory Allocation in C++38
Even though malloc and free are available in C++, their use is not recommended.
It is always preferred to use new and delete, especially when working with objects. new and delete point to the correct memory type (they are type
safe), so casts are not necessary. new invokes the constructor, allowing the initialization of
objects, and delete invokes the destructor. new figures out the size it needs to allocate, so the programmer
doesn't have to specify it. new throws an exception of type std::bad_alloc when it fails,
instead of only returning a NULL pointer like malloc.
© Miri Ben-Nissan
new and delete39
void* operator new(size_t);
void operator delete(void*);
void* operator new[](size_t);
void operator delete[](void*);
© Miri Ben-Nissan
The new & delete operators40
Do I need to check for NULL after “p = new Fred()”?
© Miri Ben-Nissan
The new & delete operator (Cont…)
41Attempt to
allocate
memory
Return the
address
Call the
handler
function
Throw
bad_alloc
Handler
installed?
yesno
success
failure
retry
© Miri Ben-Nissan
The new & delete operators (Cont…)
42
Do I need to check for NULL after “p = new Fred()”?
Do NOT check for NULL when allocating memory with new, since new will never return NULL!
The ptrwill retain its previous value.
Malloc can fail. When you call malloc, or when you get a pointer back from a function that calls malloc, you should check to ensure that the pointer you got back wasn't NULL.
© Miri Ben-Nissan
The new & delete operators (Cont…)
43
Until the early 1990s, operator new behaved very much like standard C's malloc(), as far as allocation failures were concerned. When it failed to allocated a memory block of the requested size, it would return a NULL pointer.
This behavior was changed in the early 1990s. Instead of returning a NULL pointer, new throws an exception of type std::bad_alloc to indicate a failure.
Testing the return value is utterly wrong.
© Miri Ben-Nissan
The new & delete operators (Cont…)
44
p = new CMyClass;
if (!p){ //not to be used under ISO
//compliant compilers
cout<<"allocation failure!“;
exit(1);
}
The “if” condition is never evaluated in the event of an allocation failure, because the exception thrown by new has already transferred control to a matching catch() clause, if one exists.
If no matching catch() is found, C++ automatically calls function terminate() that terminates the program unconditionally.
© Miri Ben-Nissan
45
try
{
p = new DerivedWind;
}
catch (std::bad_alloc &ba)
{
cout<<"allocation failure!“;
//...additional cleanup
}
Embedded systems don't always support exceptions. In such environments, nothrow new can be rather
useful.
The new & delete operators (Cont…)
© Miri Ben-Nissan
The new & delete operator (Cont…)
46
Before operator new throws an exception, it calls a client-specified error-handling function, called a new-handler.
To specify the out-of-memory-handling function, clients call set_new_handler, a standard library function declared in <new>:
namespace std
{
typedef void(*new_handler_ptr)();
//ptr to function
new_handler set_new_handler(new_handler_ptr p)
throw();
}
© Miri Ben-Nissan
The new & delete operator (Cont…)
47
Example:
void OutOfMem()
{
std::cerr<<“Failed to allocate memory\n”;
std::abort();
}
int main()
{
std::set_new_handler(OutOfMem);
int* pBigDataArray = new int[499999999L];
//…
}
© Miri Ben-Nissan
The new & delete operator (Cont…)
48
A well designed new-handler function must do one of the following:
Make more memory available. Install a different new-handler.De-install the new-handler, by passing NULL to set_new_handler. Like this the new operator will throw an exception.
Throw an exception.No return (abort or exit).
© Miri Ben-Nissan
The new & delete operator (Cont…)
49
Further reading at home:
Handling memory failures in different ways, depending on the class of the object being allocated.(Effective C++, pages 242-247).
© Miri Ben-Nissan
The new & delete operators (Cont…)
50
Do I need to check for NULL before “delete p”?
No!
The C++ language guarantees that delete pwill do nothing if p is equal to NULL.
Wrong: if (p != NULL)
delete p;
Right: delete p;
© Miri Ben-Nissan
The new & delete operators (Cont…)
51
What are the two steps that happen when I say delete p?
“delete p” is a two-step process: It calls the destructor, then releases the memory.
The code generated for “delete p” is functionally similar to this (assuming p is of type Fred*):
// Original code: delete p;if (p != NULL) {p->~Fred();operator delete(p);
}
© Miri Ben-Nissan
The new & delete operators (Cont…)
52
The statement p->~Fred() calls the destructor for the Fred object pointed to by p.
The statement “operator delete(p)” calls the memory de-allocation primitive,
void operator delete(void* p).
This primitive is similar in spirit to free(void* p). (Note, however, that these two are not
interchangeable; e.g., there is no guarantee that the two memory de-allocation primitives even use the same heap!)
© Miri Ben-Nissan
The new & delete operators (Cont…)
53
Operator New:Call new(sz) operator.Call the constructor.
Operator DeleteCall the destructorCall the delete(p) operator.
© Miri Ben-Nissan
Overloading new & delete54
When you overload operator new and operator delete, you’re changing only the way raw storage is allocated. The compiler will simply call your new instead of the
default version to allocate storage, then call the constructor for that storage.
So, although the compiler allocates storage and calls the constructor when it sees new, all you can change when you overload new is the storage allocation portion. (delete has a similar limitation).
© Miri Ben-Nissan
Overloading new & delete (Cont…)
55
When you overload operator new, you also replace the behavior when it runs out of memory, so you must decide what to do in your operator new. (return zero, write a loop to call the new-handler
and retry allocation, or (typically) throw a bad_alloc exception).
© Miri Ben-Nissan
Overloading global new & delete56
When the global versions of new and delete are unsatisfactory for the whole system.
If you overload the global versions, you make the defaults completely inaccessible – you can’t even call them from inside your redefinitions.
The overloaded new must take an argument of size_t (the Standard C standard type for sizes).
The return value of operator new is a void*, nota pointer to any particular type.
© Miri Ben-Nissan
Overloading global new & delete (Cont…)
57
You must return a pointer either to an object of that size (or bigger, if you have some reason to do so), or to zero if you can’t find the memory (in which case the constructor is not called!).However, if you can’t find the memory, you should
probably do something more drastic than just returning zero, like calling the new-handler or throwing an exception, to signal that there’s a problem.
© Miri Ben-Nissan
Overloading global new & delete (Cont…)
58
The operator delete takes a void* to memory that was allocated by operator new .
It’s a void* because you get that pointer afterthe destructor is called, which removes the object-ness from the piece of storage. The return type is void.
© Miri Ben-Nissan
59
Overloading global new & delete (Cont…)
#include <cstdio>
#include <cstdlib>
using namespace std;
void* operator new(size_t sz)
{
printf("operator new: %d Bytes\n", sz);
void* m = malloc(sz);
if(!m) puts("out of memory");
return m;
}
void operator delete(void* m)
{
puts("operator delete");
free(m);
}
© Miri Ben-Nissan
60
Overloading global new & delete (Cont…)
class S
{
int i[100];
public:
S() { puts("S::S()"); }
~S() { puts("S::~S()"); }
};
© Miri Ben-Nissan
61
Overloading global new & delete (Cont…)
int main()
{
puts("creating & destroying an int");
int* p = new int(47);
delete p;
puts("creating & destroying an s");
S* s = new S;
delete s;
puts("creating & destroying S[3]");
S* sa = new S[3];
delete []sa;
}
© Miri Ben-Nissan
Overloading new & delete for a class62
When you overload new and delete for a class, you’re creating static member functions.The operators are implicitly declared as static, even if
you didn’t specify it.
When the compiler sees you use new to create an object of your class, it chooses the member operator new over the global version. The global versions of new and delete are used for all
other types of objects (unless they have their own newand delete).
© Miri Ben-Nissan
63
Overloading new & delete for a class (Cont…)class MemBlock
{
public:
enum{BLOCK_SIZE=10, MAX_POOL_SIZE=100};
public:
MemBlock();
~ MemBlock();
void* operator new(size_t);
void operator delete(void*);
private:
char c[BLOCK_SIZE]; //not used, just to have
//a size of the block.
static unsigned char _pool[];
static unsigned char _alloc_map[];
};
H File
© Miri Ben-Nissan
64
Overloading new & delete for a class (Cont..)
unsigned char
MemBlock::_pool[MAX_POOL_SIZE*sizeof(MemBlock)];
//one byte for every block
unsigned char
MemBlock::_alloc_map[MAX_POOL_SIZE] = {0};
ofstream local_out("MemBlock.out"); //log file
MemBlock::MemBlock()
{ local_out << "MemBlock()\n"; }
MemBlock::~MemBlock()
{ local_out << "~MemBlock() ... "; }
CPP File
© Miri Ben-Nissan
65
Overloading new & delete for a class (Cont..)
// Size is ignored -- assume a Framis object
void* MemBlock::operator new(size_t)
{
for(int i = 0; i < MAX_POOL_SIZE; i++)
{
if(!_alloc_map[i])
{
local_out<<"using block "<<i<<"...";
_alloc_map[i] = 1; //Mark it used
return _pool + (i * sizeof(MemBlock));
}
}
local_out << "out of memory" << endl;
return NULL;
}
© Miri Ben-Nissan
66
Overloading new & delete for a class (Cont..)
void MemBlock::operator delete(void* ptr)
{
if(!ptr)
{
return; // Check for null pointer
}
// Calculate which block number it is:
unsigned long block =
(unsigned long)ptr - (unsigned long)_pool;
block /= sizeof(MemBlock);
local_out << "freeing block " << block << endl;
// Mark it free:
_alloc_map[block] = 0;
}
© Miri Ben-Nissan
67
Overloading new & delete for a class (Cont..)
int main ()
{
MemBlock* f[MemBlock::MAX_POOL_SIZE];
for(int i=0; i<MemBlock::MAX_POOL_SIZE; i++){
f[i] = new MemBlock;
}
new MemBlock; // Out of memory
delete f[10];
f[10] = 0;
MemBlock* x = new MemBlock;
delete x;
for(int j=0; j<MemBlock::MAX_POOL_SIZE; j++){
delete f[j]; // Delete f[10] OK
}
}
Main File
© Miri Ben-Nissan
Overloading new & delete for arrays 68
If you overload operator new and delete for a class, those operators are called whenever you create an object of that class.
However, if you create an array of those class objects, the global operator new is called to allocate enough storage for the array all at once, and the global operator deleteis called to release that storage.
You can control the allocation of arrays of objects by overloading the special array versions of operator new[ ] and operator delete[ ] for the class.
© Miri Ben-Nissan
69
Overloading new & delete for arrays (Cont…)
MyType* fp = new MyType[100];
MyType* fp2 = new MyType;
delete fp2; // OK
delete fp; // Not the desired effect
The destructor will be called for the MyType object pointed to by the given address, and then the storage will be released.
For fp2 this is fine, but for fp this means the other 99 destructor calls won’t be made.
The proper amount of storage will still be released, however, because it is allocated in one big chunk, and the size of the whole chunk is stashed somewhere by the allocation routine.
© Miri Ben-Nissan
Overloading new & delete for arrays (Cont…)
70
delete []fp;
The empty brackets tell the compiler to generate code that fetches the number of objects in the array, stored somewhere when the array is created, and calls the destructor for that many array objects.
© Miri Ben-Nissan
71
Overloading new & delete for arrays (Cont…)
class Widget
{
public:
enum {SZ = 10};
Widget();
~Widget();
void* operator new(size_t sz) throw();
void operator delete(void* p);
void* operator new[](size_t sz) throw();
void operator delete[](void* p);
private:
int i[SZ ];
};
© Miri Ben-Nissan
72
Overloading new & delete for arrays (Cont…)
ofstream trace("ArrayNew.out");
Widget::Widget() { trace << "*"; }
Widget::~Widget(){ trace << "~"; }
void* Widget::operator new(size_t sz)
throw (std::bad_alloc)
{
trace<<"\nWidget::new: “<<sz<<" bytes\n";
return ::new char[sz];
}
void Widget::operator delete(void* p)
{
trace << "\nWidget::delete" << endl;
::delete []p;
}
© Miri Ben-Nissan
73
Overloading new & delete for arrays (Cont…)
void* Widget::operator new[](size_t sz)
throw (std::bad_alloc)
{
trace << "\nWidget::new[]: "
<< sz << " bytes" << endl;
return ::new char[sz];
}
void Widget::operator delete[](void* p)
{
trace << "\nWidget::delete[]" << endl;
::delete []p;
}
© Miri Ben-Nissan
74
Overloading new & delete for arrays (Cont…)
int main()
{
trace << "new Widget" << endl;
Widget* w = new Widget;
trace << "\ndelete Widget" << endl;
delete w;
trace << "\nnew Widget[25]" << endl;
Widget* wa = new Widget[25];
trace << "\ndelete []Widget" << endl;
delete []wa;
return 0;
}
© Miri Ben-Nissan
Overloading new & delete for arrays (Cont…)
75
new Widget
Widget::new: 40 bytes
*
delete Widget
~
Widget::delete
new Widget[25]
Widget::new[]: 1004 bytes
*************************
delete []Widget
~~~~~~~~~~~~~~~~~~~~~~~~~
Widget::delete[]
© Miri Ben-Nissan
Overloading new & delete for arrays (Cont…)
76
After p = new Fred[n], how does the compiler know there are n objects to be destructed during delete[ ] p?
The run-time system stores the number of objects, n, somewhere where it can be retrieved if you only know the pointer, p.
There are two popular techniques that do this. Both these techniques are in use by commercial-grade compilers, both have tradeoffs, and neither is perfect.
© Miri Ben-Nissan
Overloading new & delete for arrays (Cont…)
77
These techniques are:
Over-allocate the array and put n just to the left of the first Fred object.
Use an associative array with p as the key and n as the value.
We saw those techniques before…
© Miri Ben-Nissan
Overloading new & delete for arrays (Cont…)
78
Using "over-allocation" (header) to remember the number of elements in an allocated array.
Each block, allocated or freed, contains a header.
Usually the heap simply consists of two lists of cells; one is the list of allocated cells and the other the list of free cells.
© Miri Ben-Nissan
79
Overloading new & delete for arrays (Cont…)
© Miri Ben-Nissan
80
Overloading new & delete for arrays (Cont…)
char* tmp =
(char*)operator new[](WORDSIZE + n*sizeof(Fred));
Fred* p = (Fred*) (tmp + WORDSIZE);
*(size_t*)tmp = n;
size_t i;
try
{
for (i = 0; i < n; ++i)
new(p + i) Fred(); // Placement new
}
catch (...)
{
while (i-- != 0)
(p + i)->~Fred(); // Explicit call to the destructor
operator delete[] ((char*)p - WORDSIZE);
throw;
}
p = new Fred[n] code:
© Miri Ben-Nissan
Overloading new & delete for arrays (Cont…)
81
Then the delete[] p statement becomes:
// Original code: delete[] p;
size_t n = *(size_t*)((char*)p-WORDSIZE);
while (n-- != 0)
(p + n)->~Fred();
operator delete[] ((char*)p - WORDSIZE);
© Miri Ben-Nissan
Overloading new & delete for arrays (Cont…)
82
Note that the address passed to operator delete[] is not the same as p.
If you make a programming error by saying delete pwhere you should have said delete[] p, the address that is passed to operator delete(void*) is not the address of any valid heap allocation. This will probably corrupt the heap.
© Miri Ben-Nissan
Overloading new & delete for arrays (Cont…)
83
Using an "associative array" to remember the number of elements in an allocated array
In this technique, the code for p = new Fred[n] looks something like this (where arrayLengthAssociation is the imaginary name of a hidden, global associative array that maps from void* to "size_t"):
© Miri Ben-Nissan
84
Overloading new & delete for arrays (Cont…)
Fred* p=(Fred*)operator new[] (n * sizeof(Fred));
size_t i;
try {
for (i = 0; i < n; ++i)
new(p + i) Fred(); // Placement new
}
catch (...) {
while (i-- != 0)
(p + i)->~Fred();
// Explicit call to the destructor
operator delete[] (p);
throw;
}
arrayLengthAssociation.insert(p, n);
© Miri Ben-Nissan
Overloading new & delete for arrays (Cont…)
85
Then the delete[] p statement becomes: size_t n=arrayLengthAssociation.lookup(p);
while (n-- != 0)
(p + n)->~Fred();
operator delete[] (p);
If you make a programming error by saying delete p where you should have said delete[] p, only the first Fred in the array gets destructed, but the heap maysurvive (unless you've replaced operator delete[] with something that doesn't simply call operator delete, or unless the destructors for the other Fred objects were necessary).
© Miri Ben-Nissan
Overloading new & delete for arrays (Cont…)
86
The “Over-Allocation” technique is faster, but more sensitive to the problem of programmers saying delete p rather than delete[] p.
The “associative array” technique is slower, but less sensitive to the problem of programmers saying delete p rather than delete[] p.
© Miri Ben-Nissan
Constructor calls 87
MyType* f = new MyType;
Assume we call new to allocate a MyType-sized piece of storage.
This call invokes the MyType constructor on that storage.
What happens if all the safeguards fail and the value returned by operator new is zero?
© Miri Ben-Nissan
Constructor calls (Cont…)
88
The constructor is not called in that case, so although you still have an unsuccessfully created object, at least you haven’t invoked the constructor and handed it a zero pointer.
© Miri Ben-Nissan
89
Constructor calls (Cont…)
void my_new_handler()
{
cout << "new handler called" << endl;
}
class NoMemory
{
public:
NoMemory(){cout<<"NoMemory::NoMemory()"<<endl;}
void* operator new(size_t sz) throw(bad_alloc)
{
cout << "NoMemory::operator new" << endl;
//throw bad_alloc(); // "Out of memory"
return 0;
}
};
© Miri Ben-Nissan
Constructor calls (Cont…)
90
int main()
{
set_new_handler(my_new_handler);
NoMemory* pNM = new NoMemory;
cout << “pNM = " << pNM << endl;
return 0;
}
© Miri Ben-Nissan
placement new
91
Used to construct an object on a pre-allocated storage. The overloaded operator new can take more than one
argument. The first argument is always the size of the object, which is
secretly calculated and passed by the compiler. The other arguments can be anything you want: the address
you want the object placed at, a reference to a memory allocation function or object, or anything else that is convenient for you.
© Miri Ben-Nissan
The way you pass the extra arguments to operator new during a call may seem slightly curious at first: You put the argument list ( withoutthe size_t argument, which is handled by the compiler) after the keyword new and before the class name of the object you’re creating.
placement new (Cont…)
© Miri Ben-Nissan
93
placement new (Cont…)
class X{
int i;
public:
X(int ii = 0) : i(ii) {}
~X() { cout << "X::~X()" << endl; }
void* operator new(size_t, void* loc) { return loc; }
};
int main() {
int vec[10]={};
X* xp = new(vec) X(7); // X at location vec
xp->X::~X(); // Explicit destructor call
// ONLY use with placement!
return 0;
}
example 1:
© Miri Ben-Nissan
placement new (Cont…)
94
class Data {
public:
int x;
};
void* pMemPool = malloc(256);
void* pNextAlloc = pMemPool ;
Data* pData = new (pNextAlloc) Data();
pNextAlloc = (char*)(pNextAlloc) + sizeof(Data);
// Writing to it
pData->x = 10;
// Destroying c
pData->~Data();
pNextAlloc = (char*)(pNextAlloc) - sizeof(Data);
free(pMemPool); pMemPool=NULL; pNextAlloc=NULL;
example 2: mini memory pool
© Miri Ben-Nissan
placement new (Cont…)
95
There’s only one version of operator delete , so there’s no way to say, “Use my special de-allocator for this object.”
You want to call the destructor, but you don’t want the memory to be released by the dynamic memory mechanism because it wasn’t allocated on the heap.
You can explicitly call the destructor, as in xp->X::~X(); // Explicit destructor call
You will have serious problems if you call the destructor this way for an object created on the stack because the destructor will be called again at the end of the scope.
If you call the destructor this way for an object that was created on the heap, the destructor will execute, but the memory won’t be released, which probably isn’t what you want.
© Miri Ben-Nissan
96
nothrow new and deleteextern const nothrow_t nothrow;
This constant value is used as an argument for operator new and operator new[] to indicate that these functions shall not throw an exception on failure, but return a null pointer instead. This version cannot be replaced by the user.
int main () {
cout << "Attempting to allocate 1 MB..."; char* p = new (nothrow) char [1048576]; if (p==0)
cout << "Failed!\n"; else {
cout << "Success!\n"; delete[] p;
} return 0;
}
© Miri Ben-Nissan
summary
97
The standard-provided overloads of operator new (there are also corresponding ones for array new[ ]):
void* ::operator new(std::size_t size)
throw(std::bad_alloc);
// usual plain old boring new
// usage: new T
void* ::operator new(std::size_t size,
const std::nothrow_t&) throw();
// nothrow new
// usage: new (std::nothrow) T
void* ::operator new(std::size_t size, void* ptr) throw();
// in-place (or "put-it-there") new
// usage: new (ptr) T