C memory allocation. Memory allocation
Dynamic memory allocation is necessary for efficient use of computer memory. For example, we wrote some kind of program that processes an array. When writing this program, it was necessary to declare an array, that is, give it a fixed size (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. What if we need only 20 elements, but space is allocated in memory for 100 elements, because the array declaration was static, and such use of memory is extremely inefficient.
In C++, the new and delete operations are used to dynamically allocate computer memory. The new operation allocates memory from an area of free memory, and the delete operation frees the allocated memory. The allocated memory must be freed after it is used, so the new and delete operations are used in pairs. Even if you do not explicitly release memory, it will be freed by OS resources when the program terminates. 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 an allocated memory area of type int //new is the operation of allocating free memory for the created object.
The new operation 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 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 type of data: int, float,double,char etc.
// 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 in which a dynamic variable will be created.
// new_delete.cpp: Defines the entry point for the console application. #include "stdafx.h" #include
delete ptrvalue; // freeing memory system(
// code Code::Blocks
// Dev-C++ code
B 10 line
shows a way to declare and initialize a dynamic object with nine; all you need to do is indicate the value in parentheses after the data type. The result of the program is shown in Figure 1.
Ptrvalue = 9 To continue, press any key. . .
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, rather than to create dynamic variables. Let's look at a code fragment for creating a one-dimensional dynamic array.
// declaration of a one-dimensional dynamic array of 10 elements: float *ptrarray = new float ; // where ptrarray is a pointer to an allocated area of memory for an array of real numbers of type float // in square brackets we indicate the size of the array
After the dynamic array has become unnecessary, you need to free the area of memory that was allocated for it.
// freeing memory allocated for a one-dimensional dynamic array: delete ptrarray;
After the delete operator, square brackets are placed, which indicate that a section of memory allocated for a one-dimensional array is being released. Let's develop a program in which we will create a one-dimensional dynamic array filled with random numbers."stdafx.h"
#include !}
delete ptrvalue; // freeing memory system(
// code Code::Blocks
After the delete operator, square brackets are placed, which indicate that a section of memory allocated for a one-dimensional array is being released. Let's develop a program in which we will create a one-dimensional dynamic array filled with random numbers.
using namespace std; int main(int argc, char* argv) ( srand(time(0)); // generating random numbers float *ptrarray = new float ; // creating a dynamic array of real numbers with ten elements for (int count = 0; count .
To obtain 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 we use the setprecision(2) function ,
the prototype of this function is in the header file
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 learned how to create and work with one-dimensional dynamic arrays. Now let's look at a piece of code that shows how to declare a two-dimensional dynamic array.
// declaration of a two-dimensional dynamic array of 10 elements: float **ptrarray = new float* ; // two lines in the array for (int count = 0; count< 2; count++) ptrarray = new float ; // и пять столбцов // где ptrarray – массив указателей на выделенный участок памяти под массив вещественных чисел типа float
First, a second-order pointer float **ptrarray is declared, which refers to an array of float* pointers, where the size of the array is two . After which, in the for loop, each line of the array declared in line 2 memory is allocated for five elements. The result is a two-dimensional dynamic array ptrarray. Let's consider an example of freeing the 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 using 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 delete ptrvalue; // freeing memory system( // code Code::Blocks // new_delete_array2.cpp: Defines the entry point for the console application. #include When outputting the array, the setw() function was used; if you remember, it allocates space of a given size for the output data. In our case, there are four positions for each element of the array, this allows us to align numbers of different lengths along columns (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++ The second way C++ can store information is by using a dynamic allocation system. In this method, memory is allocated to information from a free memory area as needed. The free memory area is located between the program code with its permanent memory area and the stack (Fig. 24.1). Dynamic allocation is useful when you don't know how many data items will be processed.
As the program uses the stack area, it increases downwards, that is, the program itself determines the amount of stack memory. For example, a program with a large number recursive functions will take up more stack memory than a program without recursive functions, since local variables and return addresses are stored on stacks. Memory for the program itself and global variables stands out for everything lead time program and is constant for a specific environment. Memory allocated during program execution is called dynamic. After selection dynamic memory is retained until it is explicitly freed, which can only be done using a special operation or library function. If dynamic memory is not freed before the end of the program, it is freed automatically when the program ends. However, it is a sign of good programming style to explicitly free memory that has become unnecessary. During program execution, a section of dynamic memory is available wherever the pointer addressing this section is available. Thus, the following are possible three options for working with dynamic memory, allocated in a certain block (for example, in the body of a non-main function). All variables declared in the program are located in one continuous memory area, which is called data segment. Such variables do not change their size during program execution and are called static. The data segment size may not be sufficient to accommodate large amounts of information. The way out of this situation is to use dynamic memory. Dynamic memory- this is the memory allocated to the program for its operation minus the data segment, the stack, which contains local variables of subroutines and the program body itself. Pointers are used to work with dynamic memory. With their help, access is made to areas of dynamic memory called dynamic variables. For storage dynamic variables A special area of memory called the heap is allocated. Dynamic Variables are created using special functions and operations. They exist either until the end of the program, or until the memory allocated for them is freed using special functions or operations. That is, the time of life dynamic variables– from the point of creation to the end of the program or to an explicit freeing up memory. C++ uses two ways to work with dynamic memory: In the C++ programming language for dynamic memory allocation there are new and delete operations. These operations are used to allocate and free blocks of memory. The area of memory in which these blocks are located is called free memory. The new operation allows you to allocate and make available a free area in main memory, the size of which corresponds to the data type identified by the type name. Syntax: new TypeName; new TypeName [Initializer]; The value determined is entered into the selected area initializer, which is an optional element. If successful, new returns the address of the beginning of the allocated memory. If an area of the required size cannot be allocated (there is no memory), then the new operation returns a zero address value (NULL). The syntax for using the operation is: Pointer = new TypeName[Initializer]; The new float operation allocates a 4-byte chunk of memory. The new int(15) operation allocates a 4-byte chunk of memory and initializes this chunk with the integer value 15. The syntax for using the new and delete operations involves the use of pointers. Each pointer must be declared in advance: type *PointerName; For example: float *pi; //Declaring a variable pi pi=new float; //Allocating memory for the variable pi * pi = 2.25; //Assigning a value As a type you can use, for example, standard types int, long, float, double, char. The new operator is most often used to allocate user-defined data types, such as structures, into memory: struct Node ( char *Name; int Value; Node *Next ); Node *PNode; //pointer is declared PNode = new Node; //memory is allocated PNode->Name = "Ata"; //values are assigned PNode->Value = 1; PNode->Next = NULL; An array can be used as a type name in the new operation: newArrayType When allocating dynamic memory for an array, its dimensions must be fully specified. For example: ptr = new int ;//10 elements of type int or 40 bytes ptr = new int ;//wrong, because size not determined This operation allows you to allocate an area in dynamic memory to accommodate an array of the appropriate type, but does not allow you to initialize it. As a result of execution, the new operation will return a pointer whose value is the address of the first element of the array. For example: int *n = new int; The new operation allocates a section of dynamic memory sufficient to accommodate a value of type int and writes the address of the beginning of this section to the variable n. Memory for the variable n itself (of a size sufficient to accommodate the pointer) is allocated at the compilation stage. There are two types of static variables: extern int maxind; int maxind = 1000; static int gcd(int x, int y); // Function prototype. . . static int gcd(int x, int y) ( // Implementation... ) Local, or stack, variables are variables described inside a function. Memory for such variables is allocated on the hardware stack, see section 2.3.2. Memory is allocated when entering a function or block and freed when exiting a function or block. In this case, the capture and release of memory occurs almost instantly, because the computer only modifies the register containing the address of the top of the stack. Local variables can be used in recursion because when a function is re-entered, a new set of local variables is created on the stack without destroying the previous set. For the same reason, local variables are thread-safe in parallel programming (see Section 2.6.2). Programmers call this property of a function re-entrability, from English re-enter able - ability to re-enter. This is a very important quality from the point of view of the reliability and safety of the program! A program that works with static variables does not have this property, so to protect static variables you have to use synchronization mechanisms(see 2.6.2), and the program logic becomes dramatically more complicated. You should always avoid using global and static variables when you can use local ones. The disadvantages of local variables are an extension of their advantages. Local variables are created when you enter a function and disappear when you exit, so they cannot be used as data shared between multiple functions. In addition, the size of the hardware stack is not infinite; the stack may at one point overflow (for example, during deep recursion), which will lead to a catastrophic termination of the program. Therefore, local variables should not be large. In particular, large arrays cannot be used as local variables. In addition to static and stack memory, there is also a practically unlimited memory resource called dynamic, or a bunch(heap). The program can capture sections of dynamic memory of the required size. After use, the previously captured area of dynamic memory should be freed. Dynamic memory is allocated space in the process's virtual memory between static memory and the stack. (The virtual memory mechanism was discussed in Section 2.6.) Typically, the stack is located at the highest addresses of virtual memory and grows towards smaller addresses (see Section 2.3). The program and constant data are located in low addresses, static variables are located higher. The space above static variables and below the stack is occupied by dynamic memory: program code and data, tamper-proof static variables programs max. address (2 32 -4) The dynamic memory structure is automatically maintained by the C or C++ language runtime system. Dynamic memory consists of captured and free segments, each of which is preceded by a segment descriptor. When executing a memory capture request, the execution system searches for a free segment of sufficient size and captures a segment of the required length in it. When a memory segment is freed, it is marked as free; if necessary, several consecutive free segments are merged. In the C language, the standard malloc and free functions are used to acquire and free dynamic memory; descriptions of their prototypes are contained in the standard header file "stdlib.h". (The name malloc is short for memory allocate- "memory capture".) The prototypes of these functions look like this: void *malloc(size_t n); // Capture a memory area // of n bytes in size void free(void *p); // Free a section of // memory with address p Here n is the size of the captured area in bytes, size_t is the name of one of the integer types that determine the maximum size of the captured area. The type size_t is specified in the standard header file "stdlib.h" using the typedef operator (see p. 117). This ensures the independence of the C program text from the architecture used. On the 32-bit architecture, the type size_t is defined as an unsigned integer: typedef unsigned int size_t; The malloc function returns the address of the allocated memory location, or zero if it fails (when there is no free location large enough). The free function frees a piece of memory with a given address. To set the address, a pointer of the general type void* is used. After calling the malloc function, it must be cast to a pointer to a specific type using the type cast operation, see section 3.4.11. For example, the following example grabs a 4000-byte chunk of heap memory and assigns its address to a pointer to an array of 1000 integers: int *a; // Pointer to an array of integers. . . a = (int *) malloc(1000 * sizeof(int)); The expression in the malloc function argument is 4000 because the size of the integer sizeof(int) is four bytes. To convert a pointer, a type cast (int *) is used from a generic type pointer to a pointer to an integer. Let's look at an example using dynamic memory capture. You need to enter an integer n and print the first n prime numbers. (A prime number is a number that has no nontrivial divisors.) We use the following algorithm: we sequentially check all odd numbers, starting with three (we consider two separately). We divide the next number by all prime numbers found in the previous steps of the algorithm and not exceeding the square root of the number being tested. If it is not divisible by any of these prime numbers, then it is itself prime; it is printed and added to the array of found primes. Since the required number of primes n is unknown before the program starts, it is impossible to create an array to store them in static memory. The solution is to grab space for the array in dynamic memory after entering the number n. Here is the full text of the program: #include while (k p is not prime, break; // exit the loop ) ++i; // To the next prime divisor ) if (prime) ( // If we find a prime number, a[k] = p; // then add it to the array ++k; // Increase the number of primes printf("%d ", p ); // Print a prime number if (k % 5 == 0) ( // Go to a new line printf("\n"); // after every five numbers ) ) p += 2; // To the next odd number ) if (k % 5 != 0) ( printf("\n"); // Translate the line ) // Free up dynamic memory free(a); An example of how this program works: Enter the number of primes: 50 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 C++ new and delete operators C++ uses the new and delete operators to acquire and free dynamic memory. They are part of the C++ language, unlike the malloc and free functions that are part of the C library of standard functions. Let T be some type of C or C++ language, p is a pointer to an object of type T. Then the new operator is used to grab memory of one element of type T: T*p; p = new T; For example, to capture eight bytes for a real number of type double, a fragment is used double *p; p = new double; When using new , unlike malloc , there is no need to cast a pointer from type void* to the desired type: the new operator returns a pointer to the type written after the word new . Compare two equivalent snippets in C and C++. For such an array, the memory allocated is 5 * 8 (double size) = 40 bytes. This way we know exactly how many elements there are in the array and how much memory it takes up. However, this is not always convenient. Sometimes it is necessary that the number of elements and, accordingly, the size of allocated memory for an array be determined dynamically depending on certain conditions. For example, the user can enter the size of the array himself. And in this case, we can use dynamic memory allocation to create the array. To control 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 memory of length s bytes and returns a pointer to the beginning of the allocated memory. Returns NULL if unsuccessful 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 if unsuccessful realloc() . Has a prototype Void *realloc(void *bl, unsigned ns); Resizes the previously allocated block of memory pointed to by pointer bl to ns bytes in size. If the bl pointer has a NULL value, that is, no memory was allocated, then the function's action is similar to that of malloc free() . Has a prototype Void *free(void *bl); Frees a previously allocated block of memory, the beginning of which is pointed to by the bl pointer. If we do not use this function, the dynamic memory will still be freed automatically when the program exits. However, it is still a good practice to call the free() function, which allows you to free up memory as early as possible. Let's consider using functions on a simple problem. The length of the array is unknown and is entered during program execution by the user, and also the values of all elements are entered by the user: #include 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 to manage memory for the array. The number of array elements is unknown in advance; it is represented by the variable n. First, the user enters the number of elements that fall into the variable n. After this, you need to allocate memory for a given 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 of the above functions, for the sake of universality of the return value, return a pointer of type void * as a result. But in our case, an array of type int is created, which is manipulated 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, display its values on 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 let's look at a more complex problem - dynamically allocating memory for a two-dimensional array: #include The table variable 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, that is, 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 row of the table. 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, first you need to allocate memory for all these arrays: Table = calloc(rowscount, sizeof(int*)); rows = malloc(sizeof(int)*rowscount); Next 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 the elements for each line are entered. At the end of the program, memory is released during output. In the program, memory is allocated for table rows, so this memory must be freed: Free(table[i]); And in addition, 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 So. The third type, the most interesting in this topic for us, is the dynamic type of memory. How did we work with arrays before? int a How do we work now? We allocate as much as needed: #include <
stdio.h>
#include <
stdlib.h>
int main() (size_t size; // Create a pointer to int
// – essentially an empty array. int *list; scanf("%lu" , &size);
// Allocate memory for size elements of size int list = (int *)malloc(size * sizeof(int));< size; ++i) {
scanf
(for (int i = 0 ; i < size; ++i) {
printf
(for (int i = 0 ; i" %d " , *(list + i));) //
*
// Don't forget to clean up after yourself! free(list); ) Void * malloc(size_t size); But in general, this is a function that allocates size bytes of uninitialized memory (not zeros, but garbage). #include <
stdio.h>
#include <
stdlib.h>
If the allocation was successful, a pointer to the very first byte of the allocated memory is returned. scanf( If unsuccessful - NULL. Also errno will be equal to ENOMEM (we will look at this wonderful variable later). That is, it would be more correct to write:< size; ++i) {
scanf
(for (int i = 0 ; i int main () ( size_t size; int *list; scanf (< size; ++i) {
printf
(for (int i = 0 ; i, &size); //
*
list = (int *)malloc(size * sizeof(int)); #include <
stdlib.h>
if (list == NULL ) ( goto error; ) for (int i = 0 ; i , list + i); ) for (int i = 0 ; i , *(list + i)); ) free(list); return 0 ; error: return 1 ; ) There is no need to clear a NULL pointer int main() (free(NULL);) – in the same clang everything will go fine (nothing will be done), but in more exotic cases it may well crash the program. Next to malloc and free in mana you can also see: void * calloc(size_t count, size_t size); Just like malloc will allocate memory for count objects of size bytes. The allocated memory is initialized with zeros. void * realloc (void *ptr, size_t size);
Rice. 24.1.Working with dynamic memory using the new and delete operations
Global variables are so called because they are available anywhere in the program in all its files. Therefore, global variable names must be long enough to avoid accidental names of two different variables. For example, the names x or n are not appropriate for a global variable;Stack or local memory
Dynamic memory or heap
address
memory contents
...
dynamic memory
stack
Example: Printing the first n prime numbers
return 0; )