Lesson 2 - Dynamic memory management in C++ New
In the last lesson, Introduction to pointers in C++, we introduced pointers in C++.
We already know that C++ lets us work with memory, and we have learned to pass parameters by reference to functions. However, we will start the right programming in C++ today. We will understand how memory allocation works and break free from all the limits of the length of static arrays.
Static and dynamic memory allocation
As we know, our program has to call the operating system to allocate memory, which is not entirely easy, which is why C++ tries to do as much as possible for us.
Statically allocated memory
When our program is compiled, the compiler can in many cases simply find out how much memory will be needed to run the program. When we create a variable of the int type, the compiler knows to set aside 32 bits for it. When we create an array of 100 characters, C++ again knows it needs to reserve 800 bits. If no data needs to be added while the program is running, this automatic allocation will suffice. That's basically the way we've programmed so far.
Dynamically allocated memory on the stack
You may have wondered how memory allocation for local variables (those defined inside functions) works in C++. After all, C++ doesn't know how many times we call a function and therefore how many variables will be needed in the end. This memory is really allocated dynamically at runtime. However, everything happens fully automatically. When we call a function, C++ asks for memory, and when the function ends, that memory is freed. This is why the function can't return an array. As we already know, an array isn't copied (it is not passed by value such as int), but it's treated as if it were a pointer. Since the variables disappear after the function ends, we would get a pointer somewhere where the array may no longer exist.
Dynamically allocated memory on the heap
So far, it seems that C++ is doing everything for us. So where's the problem? Imagine that you're programming an application that records, for example, items in a warehouse. Do you know in advance how big of an array you need to create for the items? Will there be 100, 1000, millions? If we declare a static array of some structures, we will always either waste space or risk that the reserved array will no longer suffice.
The problem is that sometimes we don't know how much memory will be needed to run the program and therefore C++ can't allocate it for us. Fortunately, it offers us functions that we can use to obtain any amount of memory while the program is running.
Note: The terms stack and heap were mentioned in the text. These are the 2 types of memory in RAM that the program works with. Simply put, working with the stack is faster, but it's limited in size. The heap is intended primarily for larger data, e.g. for the mentioned items in the warehouse. When we talk about memory ourselves, it'll always be allocated on the heap.
Dynamic memory allocation
The focus of work with dynamic memory in C++ is a pair of keywords - new and delete.
The new keyword tells the operating system about the amount of memory for the specific type we are going to allocate. The function returns a pointer to the beginning of the address where lies our new memory. We already know that each pointer has a certain type, resp. points to some type.
If the memory allocation fails (for example, we ran out of memory, which theoretically doesn't happen today, but we should take this case into account), the new call returns NULL (i.e. a pointer to nowhere).
Each call to new must sometimes (perhaps only at the end of the program) be followed by a call to delete, which marks the memory as free. This memory is fully under our direction and no one but us will release it for us. As soon as we no longer need any dynamically allocated memory, we should free it immediately.
Let's allocate space for int and double while the program is running
int* number = new int; double* floating_number = new double; //program delete number; number = NULL; delete floating_number; floating_number = NULL;
In the program, we work with pointers in the same way as we showed in the previous part. At the end of the program, you must free the memory using delete. Notice the subsequent assignment of NULL back to the pointer. If you call delete twice on the same pointer, the program will probably crash. It'll try to delete the memory that no longer belongs to it and the operating system will terminate it. When we assign NULL, delete doesn't delete anything, but at least the program doesn't crash, so it's safer to assign NULL to a pointer after each call to delete.
Common mistakes when working with pointers
Working with pointers is quite dangerous, because no one watches us programmers. And making a mistake in the program is very simple and you don't even have to be a beginner. Let's mention a few points that are good to pay attention to when working with pointers.
- Not freeing memory - Once we forget to free up some memory, basically nothing will happen. The problem is when we forget to free the memory inside a function that is called several times during the program run. The worst situation is when we don't free the memory in a loop. With this error, we'll of course run out of memory, the application will crash and the user will lose data and pay the competition to get a working application.:)
- Exceeding memory boundaries - As was the case with arrays, no one monitors what we store in this memory. If we save something larger than how much space we have reserved, we'll break down the memory of another part of the application. This error can occur anywhere, and we will probably look for it for a very long time, because the subsequent error is not logically related to the place in the program where our memory overflowed. It can really show up in any way:)
- Working with freed memory - It may happen that we free some memory and then try to write something to this address again. At that moment, however, we write again to a memory that doesn't belong to us, see the consequences above. Therefore, it's a good idea to store the NULL value in the pointer after freeing its memory to avoid this error.
We allocate the array as if it were a type. For example, we will allocate an array of 10 integers as follows:
int* array = new int; array = 125;
First, notice that we can actually treat the pointer as an array. You will learn more in the next part. The rule is still that C++ doesn't check for array violations. C++ allows us to access, for example, the fifteenth element (array = 15), but again we try to get to a memory that doesn't belong to us. In the best case, the program reads random data, in the worse case the program crashes.
But what is different is freeing memory. This time we have to tell the compiler that we're freeing the array. We do this using square brackets for delete:
delete  array;
Thanks to the brackets, C++ knows it needs to delete an array. It's also important that the pointer type is the same as the type of stored data (because pointers can be changed to a different type). If you change the pointer type and then try to delete it, the result will be uncertain and the program may crash or free the wrong amount of memory. In any case, the program gets into a situation that shouldn't happen in any case.
In the next lesson, Arithmetic of pointers in C++, we will learn pointer arithmetic and find that pointers in C++ are even more similar to arrays than we thought.
2 messages from 2 displayed.