stores global variables and constants;

    size is determined at compile time.

    stack

    stores local variables, function arguments and intermediate values ​​of calculations;

    the size is determined when the program is launched (usually 4 MB is allocated).

    Heap

    dynamically allocated memory;

    The OS allocates memory in chunks (as needed).

Dynamically allocated memory should be used if we do not know in advance (at the time of writing the program) how much memory we need (for example, the size of the array depends on what the user enters while the program is running) and when working with large amounts of data.

Dynamic memory, also called "heap", is explicitly allocated at the request of the program from resources operating system and controlled by a pointer. It is not automatically initialized and must be explicitly freed. Unlike static and automatic memory, dynamic memory is practically unlimited (limited only by the size of RAM) and can change while the program is running.

Working with dynamic memory in s

To work with dynamic memory The following functions are used in the C language: malloc, calloc, free, realloc. Let's consider them in more detail.

    Allocation (memory grab) : void *malloc(size_t size);

As input parameter the function takes the size of the memory to be allocated. The return value is a pointer to a heap-allocated chunk of memory. If the OS was unable to allocate memory (for example, there was not enough memory), then malloc returns 0.

    After you finish working with dynamically allocated memory, you need to free it. For this purpose, it is used free function, which returns memory under OS control: void free(void *ptr);

If dynamic memory is not freed before the end of the program, then it is freed automatically when the program ends. However, it is a sign of good programming style to explicitly deallocate unneeded memory.

Example:// memory allocation for 1,000 int elements

int * p = (int *) malloc(1000*sizeof(int));

if (p==NULL) out<< "\n память не выделена";

free(p); // return memory to the heap

2. Allocation (capture of memory): void *calloc(size_t nmemb, size_t size);

The function works similarly to malloc, but differs in syntax (instead of the size of the allocated memory, you need to specify the number of elements and the size of one element) and in that the allocated memory will be reset to zero. For example, after executing int * p = (int *) calloc(1000, sizeof(int)) p will point to the beginning of an int array of 1000 elements initialized to zero.

3. Changing the memory size: void *realloc(void *ptr, size_t size);

The function changes the size of the allocated memory (pointed to by ptr, derived from call malloc, calloc or realloc). If the size specified in the parameter size greater than the one allocated under the pointer ptr, then it is checked whether it is possible to allocate the missing memory cells in a row with those already allocated. If there is not enough space, then a new piece of memory is allocated with the size size and pointer data ptr are copied to the beginning of the new section.

During the execution of the program, a section of dynamic memory is available wherever a pointer is available that addresses this section. Thus, the following three variants of working with dynamic memory allocated in some block (for example, in the body of a non-main function) are possible.

    A pointer (to a dynamic memory area) is defined as a local automatic memory object. In this case, the allocated memory will not be available when exiting the pointer localization block, and must be freed before exiting the block.

( int* p= (int *) calloc(n, sizeof(int))

free(p); // free dyn. memory

    The pointer is defined as a local static storage object. Dynamic memory allocated once in a block is available through a pointer each time the block is re-entered. Memory should be freed only when it is no longer in use.

(static int* p = (int *) calloc(n, sizeof(int));

p= (int *) calloc(n, sizeof(int));

f(50); //highlight din. memory to be freed

f1(100); //highlight din. memory (first access)

f1(100); // work with din. memory

f1(0); // free dyn. memory

    The pointer is a global object with respect to the block. Dynamic memory is available in all blocks where the pointer is "visible". Memory should be freed only when it is no longer used.

int*pG; //working pointer for din. memory (global variable)

void init(int size)

for (i=0; i< size; i++) //цикл ввода чисел

( printf("x[%d]=",i);

scanf("%d", &pG[i]);

int sum (int size)

for (i=0; i< size; i++) //цикл суммирования

// memory allocation

pG= (int *) calloc(n, sizeof(int));

// work with dynamic memory

printf(\ns=%d\n",sum(n));

free (pG); pG=NULL; // deallocate memory

Working with dynamic memory in C++

C++ has its own mechanism for allocating and freeing memory - these are functions new and delete. Usage example new: int * p = new int; // allocation of memory for 1000 e-ths I.e. when using the function new no need to cast a pointer and no need to use sizeof(). Freeing a selection with new memory is handled by the following call: delete p; If you need to allocate memory for one element, then you can use int * q = new int; or int * q = new int(10); // the allocated int will be initialized with the value 10 in this case the deletion will look like this: delete q;

Last update: 05/28/2017

When creating an array with a fixed size, a certain amount of memory is allocated for it. For example, let's have an array with five elements:

double numbers = (1.0, 2.0, 3.0, 4.0, 5.0);

For such an array, memory is allocated 5 * 8 (the size of the double type) = 40 bytes. Thus, we know exactly how many elements are in the array and how much memory it takes. However, this is not always convenient. Sometimes it is necessary that the number of elements and, accordingly, the size of the allocated memory for an array are determined dynamically depending on some conditions. For example, the user himself can enter the size of the array. And in this case, we can use dynamic memory allocation to create an array.

To manage dynamic memory allocation, a number of functions are used, which are defined in the stdlib.h header file:

    malloc() . Has a prototype

    Void *malloc(unsigned s);

    Allocates s bytes of memory and returns a pointer to the beginning of the allocated memory. Returns NULL on failure

    calloc() . Has a prototype

    Void *calloc(unsigned n, unsigned m);

    Allocates memory for n elements of m bytes each and returns a pointer to the beginning of the allocated memory. Returns NULL on failure

    realloc() . Has a prototype

    Void *realloc(void *bl, unsigned ns);

    Changes the size of the previously allocated block of memory pointed to by bl to ns bytes. If the pointer bl is NULL , i.e. no memory was allocated, then the action of the function is similar to the action of malloc

    free() . Has a prototype

    void *free(void *bl);

    Frees the previously allocated block of memory pointed to by the bl pointer.

    If we do not use this function, then the dynamic memory will still be freed automatically when the program ends. However, it is still good practice to call the free() function, which allows you to free memory as soon as possible.

Consider the application of functions on a simple problem. The length of the array is unknown and is entered at runtime by the user, and also the values ​​of all elements are entered by the user:

#include #include int main(void) ( int *block; // pointer to block of memory int n; // number of array elements // enter the number of elements printf("Size of array="); scanf("%d", &n); / / allocate memory for the array // the malloc function returns a pointer of type void* // which is automatically converted to type int* block = malloc(n * sizeof(int)); // enter numbers into the array for(int i=0;i

Console output of the program:

Size of array=5 block=23 block=-4 block=0 block=17 block=81 23 -4 0 17 81

Here, a block pointer of type int is defined for memory management for an array. The number of array elements is not known in advance, it is represented by the variable n.

First, the user enters the number of elements that goes into the variable n. After that, you need to allocate memory for this number of elements. To allocate memory here, we could use any of the three functions described above: malloc, calloc, realloc. But specifically in this situation, we will use the malloc function:

Block = malloc(n * sizeof(int));

First of all, it should be noted that all three functions mentioned above return a void * type pointer as a result for the universality of the returned value. But in our case, an array of type int is created, which is managed by a pointer of type int * , so the result of the malloc function is implicitly cast to type int * .

The malloc function itself is passed the number of bytes for the allocated block. This number is quite simple to calculate: just multiply the number of elements by the size of one element n * sizeof(int) .

After all actions are completed, the memory is freed using the free() function:

free(block);

It is important that after executing this function, we will no longer be able to use the array, for example, print its values ​​to the console:

free(block); for(int i=0;i

And if we try to do this, we will get undefined values.

Instead of the malloc function, we could similarly use the calloc() function, which takes the number of elements and the size of one element:

Block = calloc(n, sizeof(int));

Or you could also use the realloc() function:

int *block = NULL; block = realloc(block, n * sizeof(int));

When using realloc, it is desirable (in some environments, for example, in Visual Studio, mandatory) to initialize the pointer to at least NULL.

But in general, all three calls in this case would have a similar effect:

Block = malloc(n * sizeof(int)); block = calloc(n, sizeof(int)); block = realloc(block, n * sizeof(int));

Now consider a more complex problem - dynamic memory allocation for a two-dimensional array:

#include #include int main(void) ( int **table; // pointer to a block of memory for an array of pointers int *rows; // pointer to a block of memory to store information on rows int rowscount; // number of rows int d; // input number / / enter the number of rows printf("Rows count="); scanf("%d", &rowscount); // allocate memory for a two-dimensional array table = calloc(rowscount, sizeof(int*)); rows = malloc(sizeof(int )*rowscount); // loop through rows for (int i = 0; i

The variable table represents a pointer to an array of pointers of type int* . Each table[i] pointer in this array represents a pointer to a subarray of int elements, i.e. individual table rows. And the table variable actually represents a pointer to an array of pointers to table rows.

To store the number of elements in each subarray, a rows pointer of type int is defined. It actually stores the number of columns for each table row.

First, the number of rows is entered into the rowscount variable. The number of rows is the number of pointers in the array pointed to by the table pointer. And furthermore, the number of rows is the number of elements in the dynamic array pointed to by the rows pointer. Therefore, it is first necessary to allocate memory for all these arrays:

Table = calloc(rowscount, sizeof(int*)); rows = malloc(sizeof(int)*rowscount);

Further in the loop, the number of columns for each row is entered. The entered value goes into the rows array. And in accordance with the entered value, the required memory size is allocated for each line:

Scanf("%d", &rows[i]); table[i] = calloc(rows[i], sizeof(int));

Then, elements are entered for each row.

At the end of the program's output, the memory is freed. The program allocates memory for table rows, so this memory must be freed:

free(table[i]);

And besides, the memory allocated for the table and rows pointers is freed:

free(table); free(rows);

Console output of the program:

Rows count=2 Columns count for 1=3 table=1 table=2 table=3 Columns count for 2=2 table=4 table=5 1 2 3 4 5

Dynamic memory allocation is necessary for the efficient use of computer memory. For example, we wrote some program that processes an array. When writing this program, it was necessary to declare an array, that is, set a fixed size for it (for example, from 0 to 100 elements). Then this program will not be universal, because it can process an array of no more than 100 elements. And if we need only 20 elements, but the memory will allocate space for 100 elements, because the array declaration was static, and such memory usage is extremely inefficient.

In C++, the new and delete operators are designed to dynamically allocate computer memory. The new operation allocates memory from the free memory area, and the delete operation releases the allocated memory. Allocated memory, after its use, must be released, so the new and delete operations are used in pairs. Even if you do not release memory explicitly, it will be freed by OS resources when the program ends. I still recommend not to forget about the delete operation.

// example of using the operation new int *ptrvalue = new int; //where ptrvalue is a pointer to the allocated memory area of ​​type int //new is the operation of allocating free memory for the created object.

The new operator creates an object of the given type, allocates memory for it, and returns a pointer of the correct type to the given memory location. If the memory cannot be allocated, for example, if there are no free areas, then a null pointer is returned, that is, the pointer will return the value 0. Memory allocation is possible for any data type: int, float,double,char etc.

// an example of using the delete operation: delete ptrvalue; // where ptrvalue is a pointer to an allocated memory area of ​​type int // delete is a memory release operation

Let's develop a program that will create a dynamic variable.

// new_delete.cpp: defines the entry point for the console application. #include "stdafx.h" #include << "ptrvalue = "<< *ptrvalue << endl; delete ptrvalue; // release memory system("pause"); return 0; }

// code Code::Blocks

// Dev-C++ code

// new_delete.cpp: defines the entry point for the console application. #include using namespace std; int main(int argc, char* argv) ( int *ptrvalue = new int; // dynamic allocation of memory for an object of type int *ptrvalue = 9; // object initialization through a pointer //int *ptrvalue = new int (9); initialization can be performed immediately when declaring a dynamic object cout<< "ptrvalue = "<< *ptrvalue << endl; delete ptrvalue; // release memory return 0; )

(!LANG:In line 10 shows how to declare and initialize a dynamic object with a nine, all you need is to specify the value in parentheses after the data type. The result of the program is shown in Figure 1.

Ptrvalue = 9 Press any key to continue. . .

Figure 1 - Dynamic variable

Creating dynamic arrays

As mentioned earlier, arrays can also be dynamic. Most often, the new and delete operations are used to create dynamic arrays, not to create dynamic variables. Consider a code fragment for creating a one-dimensional dynamic array.

// declaration of a one-dimensional dynamic array with 10 elements: float *ptrarray = new float ; // where ptrarray is a pointer to the allocated memory area for an array of real numbers of the float type // in square brackets indicate the size of the array

After the dynamic array has become unnecessary, you need to free up the memory area that was allocated for it.

// release of memory allocated for a one-dimensional dynamic array: delete ptrarray;

After the delete operator, square brackets are placed, which indicate that a portion of memory allocated for a one-dimensional array is being freed. Let's develop a program in which we will create a one-dimensional dynamic array filled with random numbers.

// new_delete_array.cpp: defines the entry point for the console application. #include"stdafx.h" #include !} // in header file // in header file < 10; count++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 cout << "array = "; for (int count = 0; count < 10; count++) cout << setprecision(2) << ptrarray << " "; delete ptrarray; // высвобождение памяти cout << endl; system("pause"); return 0; }

// code Code::Blocks

// Dev-C++ code

// new_delete_array.cpp: defines the entry point for the console application. #include // in header file contains the prototype of the time() function #include // in header file contains the prototype of the setprecision() function #include #include using namespace std; int main(int argc, char* argv) ( srand(time(0)); // generating random numbers float *ptrarray = new float ; // creating a ten-element dynamic array of real numbers for (int count = 0; count< 10; count++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 cout << "array = "; for (int count = 0; count < 10; count++) cout << setprecision(2) << ptrarray << " "; delete ptrarray; // высвобождение памяти cout << endl; system("pause"); return 0; }

The created one-dimensional dynamic array is filled with random real numbers obtained using the random number generation functions, and the numbers are generated in the range from 1 to 10, the interval is set as follows - rand() % 10 + 1 . To get random real numbers, a division operation is performed, using an explicit cast to the real type of the denominator - float((rand() % 10 + 1)) . To show only two decimal places use the setprecision(2) function , the prototype of this function is in the header file . The time(0) function seeds the random number generator with a temporary value, thus, it turns out to reproduce the randomness of the occurrence of numbers (see Figure 2).

Array = 0.8 0.25 0.86 0.5 2.2 10 1.2 0.33 0.89 3.5 Press any key to continue. . .

Figure 2 - Dynamic array in C++

Upon completion of work with the array, it is deleted, thus freeing up the memory allocated for its storage.

We have learned how to create and work with one-dimensional dynamic arrays. Now let's look at a code snippet that shows how a two-dimensional dynamic array is declared.

// declaration of a two-dimensional dynamic array with 10 elements: float **ptrarray = new float* ; // two strings in the array for (int count = 0; count< 2; count++) ptrarray = new float ; // и пять столбцов // где ptrarray – массив указателей на выделенный участок памяти под массив вещественных чисел типа float

First, a float **ptrarray second-order pointer is declared, which refers to an array of float* pointers, where the array size is two . After that, in the for loop, each line of the array declared in line 2 memory is allocated for five elements. As a result, a two-dimensional dynamic array ptrarray is obtained. Let's consider an example of releasing memory allocated for a two-dimensional dynamic array.

// freeing memory allocated for a two-dimensional dynamic array: for (int count = 0; count< 2; count++) delete ptrarray; // где 2 – количество строк в массиве

Declaring and deleting a two-dimensional dynamic array is done with a loop, as shown above, you need to understand and remember how this is done. Let's develop a program in which we will create a two-dimensional dynamic array.

// new_delete_array2.cpp: defines the entry point for the console application. #include "stdafx.h" #include #include #include < 2; count++) ptrarray = new float ; // и пять столбцов // заполнение массива for (int count_row = 0; count_row < 2; count_row++) for (int count_column = 0; count_column < 5; count_column++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 // вывод массива for (int count_row = 0; count_row < 2; count_row++) { for (int count_column = 0; count_column < 5; count_column++) cout << setw(4) <

// code Code::Blocks

// Dev-C++ code

// new_delete_array2.cpp: defines the entry point for the console application. #include #include #include #include using namespace std; int main(int argc, char* argv) ( srand(time(0)); // generate random numbers // dynamic creation of a ten-element two-dimensional array of real numbers float **ptrarray = new float* ; // two strings in the array for (int count = 0; count< 2; count++) ptrarray = new float ; // и пять столбцов // заполнение массива for (int count_row = 0; count_row < 2; count_row++) for (int count_column = 0; count_column < 5; count_column++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 // вывод массива for (int count_row = 0; count_row < 2; count_row++) { for (int count_column = 0; count_column < 5; count_column++) cout << setw(4) <

When outputting the array, the setw() function was used, if you have not forgotten, then it allocates a place of a given size for the output data. In our case, there are four positions for each element of the array, this allows you to align, in columns, numbers of different lengths (see Figure 3).

2.7 10 0.33 3 1.4 6 0.67 0.86 1.2 0.44 Press any key to continue. . .

Figure 3 - Dynamic array in C++

Working with dynamic memory is often a bottleneck in many algorithms, unless special tricks are used.

In this article, I will look at a couple of such techniques. The examples in the article differ (for example, from this one) in that the new and delete operators are overloaded, and due to this, the syntactic constructions will be minimalistic, and the program modification will be simple. Pitfalls found in the process are also described (of course, gurus who read the standard from cover to cover will not be surprised).

0. Do we need manual work with memory?

First of all, let's check how smart the allocator can speed up the work with memory.

Let's write simple tests for C++ and C# (C# is known for its excellent memory manager, which divides objects into generations, uses different pools for objects of different sizes, etc.).

Class Node ( public: Node* next; ); // ... for (int i = 0; i< 10000000; i++) { Node* v = new Node(); }

Class Node ( public Node next; ) // ... for (int l = 0; l< 10000000; l++) { var v = new Node(); }

Despite all the “spherical vacuum” of the example, the time difference turned out to be 10 times (62 ms versus 650 ms). In addition, the c# example is finished, and according to the rules of good manners in c++, the selected objects must be deleted, which will further increase the gap (up to 2580 ms).

1. Object pool

The obvious solution is to take a large block of memory from the OS and break it into equal blocks of size of sizeof (Node), take a block from the pool when memory is allocated, and return it to the pool when freed. The easiest way to organize a pool is with a singly linked list (stack).

Since the goal is to tamper with the program as little as possible, all that can be done is to add a BlockAlloc mixin to the Node class:
class Node: public BlockAlloc

First of all, we need a pool of large blocks (pages) that we take from the OS or C-runtime. It can be organized on top of the malloc and free functions, but for greater efficiency (to skip an extra layer of abstraction), we use VirtualAlloc/VirtualFree. These functions allocate memory in blocks that are multiples of 4K and also reserve the process address space in blocks that are multiples of 64K. By specifying the commit and reserve options at the same time, we skip another level of abstraction, reserving address space and allocating memory pages in one call.

PagePool class

inline size_t align(size_t x, size_t a) ( return ((x-1) | (a-1)) + 1; ) //#define align(x, a) ((((x)-1) | ( (a)-1)) + 1) template class PagePool ( public: void* GetPage() ( void* page = VirtualAlloc(NULL, PageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); pages.push_back(page); return page; ) ~PagePool() ( for (vector ::iterator i = pages.begin(); i != pages.end(); ++i) ( VirtualFree(*i, 0, MEM_RELEASE); ) ) private: vector pages; );

Then we organize a pool of blocks of a given size

BlockPool class

template class BlockPool: PagePool ( public: BlockPool() : head(NULL) ( BlockSize = align(sizeof(T), Alignment); count = PageSize / BlockSize; ) void* AllocBlock() ( // todo: lock(this) if (!head) FormatNewPage(); void* tmp = head; head = *(void**)head; return tmp; ) void FreeBlock(void* tmp) ( // todo: lock(this) *(void**)tmp = head; head = tmp; ) private: void* head; size_t BlockSize; size_t count; void FormatNewPage() ( void* tmp = GetPage(); head = tmp; for(size_t i = 0; i< count-1; i++) { void* next = (char*)tmp + BlockSize; *(void**)tmp = next; tmp = next; } *(void**)tmp = NULL; } };

comment // todo: lock(this) places that require inter-thread synchronization are marked (for example, use EnterCriticalSection or boost::mutex).

Let me explain why when “formatting” the page, the FreeBlock abstraction is not used to add a block to the pool. If it were written something like

For (size_t i = 0; i< PageSize; i += BlockSize) FreeBlock((char*)tmp+i);

Then the page according to the FIFO principle would be marked up “in reverse”:

Several blocks requested from the pool in a row would have descending addresses. And the processor does not like to go backwards, this breaks Prefetch ( UPD: Not relevant for modern processors). If you do markup in a loop
for (size_t i = PageSize-(BlockSize-(PageSize%BlockSize)); i != 0; i -= BlockSize) FreeBlock...
then the markup cycle would go back to the addresses.

Now that the preparations are done, we can describe the mixin class.
template class BlockAlloc ( public: static void* operator new(size_t s) ( if (s != sizeof(T)) ( return::operator new(s); ) return pool.AllocBlock(); ) static void operator delete(void * m, size_t s) ( if (s != sizeof(T)) ( ::operator delete(m); ) else if (m != NULL) ( pool.FreeBlock(m); ) ) // todo: implement nothrow_t overloads, according to borisko" comment // http://habrahabr.ru/post/148657/#comment_5020297 // Avoid hiding placement new that"s needed by the stl containers... static void* operator new(size_t, void * m) ( return m; ) // ...and the warning about missing placement delete... static void operator delete(void*, void*) ( ) private: static BlockPool pool; ); template block pool BlockAlloc ::pool;

Explain why checks are needed if (s != sizeof(T))
When do they work? Then, when a class is created/deleted, inherited from the base T.
Descendants will use the usual new/delete, but BlockAlloc can also be mixed in. Thus, we can easily and safely determine which classes should use the pools without fear of breaking something in the program. Multiple inheritance also works great with this mixin.

Ready. We inherit Node from BlockAlloc and re-run the test.
The test time is now 120 ms. 5 times faster. But in c#, the allocator is still better. It's probably not just a linked list. (If, however, immediately after new, immediately call delete, and thereby not waste a lot of memory, fitting data into the cache, we get 62 ms. Strange. Exactly like the .NET CLR, as if it returns the freed local variables immediately to the corresponding pool, without waiting for GC)

2. The container and its colorful contents

How often do you come across classes that store a lot of different child objects, such that the lifetime of the latter is no longer than the lifetime of the parent?

For example, it could be an XmlDocument class filled with Node and Attribute classes, as well as c-strings (char*) taken from the text inside the nodes. Or a list of files and directories in the file manager that are loaded once when rereading the directory and do not change anymore.

As shown in the introduction, delete is more expensive than new. The idea of ​​the second part of the article is to allocate memory for child objects in a large block associated with the Parent object. When the parent object is deleted, the destructors will be called in the children, as usual, but the memory will not need to be returned - it will be freed in one large block.

Let's create the PointerBumpAllocator class, which can bite off chunks of different sizes from a large block and allocate a new large block when the old one is exhausted.

PointerBumpAllocator class

template class PointerBumpAllocator ( public: PointerBumpAllocator() : free(0) ( ) void* AllocBlock(size_t block) ( // todo: lock(this) block = align(block, Alignment); if (block > free) ( free = align (block, PageSize); head = GetPage(free); ) void* tmp = head; head = (char*)head + block; free -= block; return tmp; ) ~PointerBumpAllocator() ( for (vector ::iterator i = pages.begin(); i != pages.end(); ++i) ( VirtualFree(*i, 0, MEM_RELEASE); ) ) private: void* GetPage(size_t size) ( void* page = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); pages.push_back(page) ; return page; ) vector pages; void*head; size_t free; ); typedef PointerBumpAllocator<>DefaultAllocator;

Finally, let's describe the ChildObject mixin with overloaded new and delete that access the given allocator:

Template struct ChildObject ( static void* operator new(size_t s, A& allocator) ( return allocator.AllocBlock(s); ) static void* operator new(size_t s, A* allocator) ( return allocator->AllocBlock(s); ) static void operator delete(void*, size_t) ( ) // *1 static void operator delete(void*, A*) ( ) static void operator delete(void*, A&) ( ) private: static void* operator new(size_t s ); );

In this case, in addition to adding a mixin to the child class, you will also need to fix all calls to new (or use the "factory" pattern). The syntax for the new operator would be:

New(...parameters for operator...) ChildObject(...parameters for constructor...)

For convenience, I have defined two new operators that take either A& or A*.
If the allocator is added to the parent class as a member, the first option is more convenient:
node = new(allocator) XmlNode(nodename);
If the allocator is added as an ancestor (impurity), the second one is more convenient:
node = new(this) XmlNode(nodename);

There is no special syntax for calling delete, the compiler will call the standard delete (marked *1) no matter which new operator was used to create the object. That is, the delete syntax is normal:
delete node;

If an exception occurs in the constructor of ChildObject (or its successor), delete is called with a signature that matches the signature of the new operator used to create this object (the first size_t parameter will be replaced with void*).

Placing the new operator in the private section prevents calling new without specifying an allocator.

I will give a complete example of using the Allocator-ChildObject pair:

Example

class XmlDocument: public DefaultAllocator ( public: ~XmlDocument() ( for (vector ::iterator i = nodes.begin(); i != nodes.end(); ++i) ( delete (*i); ​​) ) void AddNode(char* content, char* name) ( char* c = (char*)AllocBlock(strlen(content)+1); strcpy(c, content); char* n = (char*)AllocBlock(strlen(name)+1); strcpy(n, content); nodes.push_back(new(this) XmlNode(c, n)); ) class XmlNode: public ChildObject ( public: XmlNode(char* _content, char* _name) : content(_content), name(_name) ( ) private: char* content; char* name; ); private:vector nodes; );

Conclusion. The article was written 1.5 years ago for the sandbox, but alas, the moderator did not like it.

C++ supports three basic types allocation(or more "distributions") memory, two of which we are already familiar with:

Static memory allocation holds for and variables. Memory is allocated once, at the start of the program, and is retained throughout the entire program.

Automatic Memory Allocation is performed for and . Memory is allocated when the block containing these variables is entered and removed when it is exited.

Dynamic memory allocation is the topic of this lesson.

Dynamic allocation of variables

Both static and automatic memory allocation have two properties in common:

How does dynamic memory allocation work?

Your computer has memory (maybe a lot of it) that is available for use by programs. When you run a program, your operating system loads that program into some part of that memory. And this memory used by your program is divided into several parts, each of which performs a specific task. One part contains your code, the other is used to perform normal operations (tracking called functions, creating and destroying global and local variables, etc.). We will talk about it later. However, most of the available memory is simply there waiting for allocation requests from programs.

When you dynamically allocate memory, you are asking the operating system to reserve some of that memory for your program to use. If the OS can fulfill this request, then the address of that memory is returned back to your program. From now on, your program will be able to use this memory as it wishes. When you have already done everything that was necessary with this memory, then it needs to be returned back to the operating system, for distribution among other requests.

Unlike static or automatic memory allocation, the program itself is responsible for requesting and returning dynamically allocated memory.

Freeing up memory

When you dynamically allocate a variable, you can also initialize it with or uniform initialization (in C++11):

int *ptr1 = new int(7); // use direct initialization int *ptr2 = new int ( 8 ); // use uniform initialization

When everything that was needed has already been done with a dynamically allocated variable, you need to explicitly tell C++ to free this memory. For variables, this is done with operator delete:

// Assume ptr has already been allocated with new delete ptr; // return the memory pointed to by ptr back to the operating system ptr = 0; // make ptr null (use nullptr instead of 0 in C++11)

The delete operator doesn't actually delete anything. It simply returns the memory that was previously allocated back to the operating system. The operating system can then reassign that memory to another application (or to the same one again).

Although it may seem that we are removing variable but it's not! The pointer variable still has the same scope as before and can be assigned a new value just like any other variable.

Note that deleting a pointer that does not point to dynamically allocated memory can lead to problems.

hanging pointers

C++ makes no guarantees about what happens to the contents of the freed memory or to the value of the pointer being deleted. In most cases, the memory returned to the operating system will contain the same values ​​that it had before release, and the pointer will continue to point to only the already freed (deleted) memory.

A pointer that points to freed memory is called hanging pointer. Dereferencing or deleting a dangling pointer will produce unexpected results. Consider the following program:

#include int main() ( int *ptr = new int; *ptr = 8; // put the value in the allocated memory location delete ptr; // return the memory back to the operating system. ptr is now a dangling pointer std::cout<< *ptr; // разыменование висячего указателя приведёт к неожиданным результатам delete ptr; // попытка освободить память снова приведёт к неожиданным результатам также return 0; }

#include

int main()

int * ptr = new int ; // dynamically allocate an integer variable

* ptr = 8 ; // put the value in the allocated memory location

delete ptr ; // return the memory back to the operating system. ptr is now a dangling pointer

std::cout<< * ptr ; // dereferencing a dangling pointer will lead to unexpected results

delete ptr ; // trying to free memory again will lead to unexpected results as well

return 0 ;

In the program above, the value 8, which was previously assigned to a dynamic variable, may or may not still be there after it is freed. It is also possible that the freed memory may already have been allocated to another application (or for the operating system's own use), and attempting to access it will cause the operating system to automatically terminate your program.

The process of freeing memory can also lead to the creation several hanging pointers. Consider the following example:

#include int main() ( int *ptr = new int; // dynamically allocate an integer variable int *otherPtr = ptr; // otherPtr now points to the same allocated memory as ptr delete ptr; // return the memory back to the operating system ptr and otherPtr are now dangling pointers ptr = 0; // ptr is now nullptr // However, otherPtr is still a dangling pointer! return 0; )

#include

int main()

int * ptr = new int ; // dynamically allocate an integer variable

int * otherPtr = ptr ; // otherPtr now points to the same allocated memory as ptr

delete ptr ; // return the memory back to the operating system. ptr and otherPtr are now dangling pointers

ptr = 0 ; // ptr is now nullptr

// However, otherPtr is still a dangling pointer!

return 0 ;

First, try to avoid situations where multiple pointers point to the same piece of allocated memory. If this is not possible, then clarify which pointer of all "owns" the memory (and is responsible for deleting it), and which pointers simply access it.

Secondly, when you delete a pointer, and if it doesn't exit immediately after deletion, then it must be made null, i.e. assign the value 0 (or in C++11). By "out of scope immediately after deletion" I mean that you delete the pointer at the very end of the block in which it is declared.

Rule: Set deleted pointers to 0 (or nullptr in C++11) unless they go out of scope immediately upon deletion.

new operator

When memory is requested from the operating system, in rare cases it may not be available (i.e., it may not be available).

By default, if the new operator did not work, the memory was not allocated, then a exception bad_alloc. If this exception is handled incorrectly (which it will be, since we haven't looked at exceptions and handling them yet), then the program will simply abort (crash) with an unhandled exception error.

In many cases, the process of throwing an exception with new (as well as crashing the program) is undesirable, so there is an alternative form of new that returns a null pointer if memory cannot be allocated. You just need to add std::nothrow constant between the new keyword and the data type:

int *value = new (std::nothrow) int; // pointer value will become null if dynamic allocation of an integer variable fails

In the example above, if new does not return a pointer with dynamically allocated memory, then a null pointer will be returned.

Dereferencing it is also not recommended, as this will lead to unexpected results (most likely, to a crash in the program). Therefore, the best practice is to check all requests for memory allocation, to ensure that these requests are executed successfully and the memory is allocated:

int *value = new (std::nothrow) int; // request to allocate dynamic memory for an integer value if (!value) // handle the case when new returns null (i.e. no memory is allocated) ( // Handling this case std::cout<< "Could not allocate memory"; }

Since non-allocation of memory by the new operator is extremely rare, programmers usually forget to do this check!

Null pointers and dynamic memory allocation

Null pointers (pointers with value 0 or nullptr) are especially useful in dynamic memory allocation. Their presence, as it were, tells us: "No memory has been allocated to this pointer." And this, in turn, can be used to perform conditional memory allocation:

// If ptr has not been allocated memory yet, then allocate it if (!ptr) ptr = new int;

Removing the null pointer does not affect anything. So the following is not necessary:

if (ptr) delete ptr;

if (ptr)

delete ptr ;

Instead, you can just write:

delete ptr ;

If ptr is non-null, then the dynamically allocated variable will be removed. If the pointer value is null, then nothing will happen.

Memory leak

Dynamically allocated memory has no scope, i.e. it remains allocated until it is explicitly freed, or until your program terminates its execution (and the operating system flushes all memory buffers itself). However, the pointers used to store dynamically allocated memory addresses follow the rules of normal variable scoping. This mismatch can cause interesting behavior. For example:

void doSomething() ( int *ptr = new int; )