Lesson 2 - Dynamic memory allocation in the C language
In the previous lesson, Introduction to pointers in the C language, we introduced the C language pointers. We already know that C lets us manage memory and we learned to pass function parameters by reference. The real C programming will start for us with today's tutorial. We're going to understand how memory allocation works and we'll break out of all length limits of static arrays and strings.
Static and dynamic memory allocation
As we know, our program has to ask the operating system for memory, which is not quite easy, and that's why C is trying to make the most of the work for us.
Statically allocated memory
When our program is being compiled, the compiler can easily determine how
much memory it'll needed in a large number of cases. When we create an
int
variable, C knows it should allocate 32 bits for it. When we
create a 100-character array, C again knows that it should reserve 800 bits. If
there's no need to add any data when the program is already running, this
automatic allocation is enough. This is basically the way we have been
programming so far.
Dynamically allocated memory on the stack
It might crossed your mind how memory allocation for local variables
(variables defined within functions) works in C. C doesn't know how many times
we're going to call a function and therefore how many variables will be needed
in the end. This memory is really allocated dynamically at
runtime. But everything happens automatically again. Every time we call a
function, C asks for memory and, when the function terminates, this memory is
freed. This is why functions can't return arrays created in them. This is
because, as we already know, arrays are not copied (are not passed as values
like e.g. int
s), we work with them as if they were pointers. And
since the local variables will be destroyed once a function terminates, we'd get
a pointer pointing somewhere where no array may no longer exist.
Dynamically allocated memory on the heap
So far, it looks like C does everything for us. So where's the problem? Imagine that you are programming an application that registers items in a warehouse, for example. Do you know in advance how big array you should create for the items? Will there be 100 items, 1000, millions? If we declared a static array of some structures, we'll either waste space or risk that the reserved array won't be enough. And we don't even have to go to extremes, just think about how to store a string exactly as long as the user has entered it.
The problem is that sometimes we don't know how much memory we're going to need until running the program, so C can't allocate it for us. Fortunately, we have functions available that allow us to ask for any amount of memory at runtime.
The terms stack and heap were mentioned in the text. Those are two types of the RAM memory that the program works with. In a simplified way, we can say that working with the stack is faster, but it's limited in size. The heap is primarily designed for larger data, e.g. for the mentioned items of the warehouse. When we ask for memory ourselves, it'll always be allocated on the heap.
Dynamic memory allocation
The key when working with dynamic memory in C is a pair of functions -
malloc()
and free()
.
malloc()
malloc()
asks the operating system for any amount of memory (we
specify how much we need in bytes as the function parameter).
The function returns a pointer to the first address where our new memory starts
from. We already know that every pointer is of a certain type, in other words it
points to data of some type. To make malloc()
universal, it returns
a pointer to the void
type. We should always cast its result to the
pointer type we need.
If the memory allocation fails (for example we run out of memory,
theoretically it shouldn't happen nowadays, but we should think about this case
too), malloc()
returns NULL
(a pointer to nowhere).
When allocating memory, we'll always use the sizeof()
function
since we never know how large a certain data type is on a particular operating
system is (e.g. int
can occupy either 16 or 32 bits).
sizeof()
calls are replaced with constants by the compiler so it
doesn't affect the program speed in any way.
free()
Each time we call malloc()
, we must call somewhere (even at the
end of the program) the free()
function to mark the memory as free
again. This memory is fully in our hands and no one other than us will free it
for us. As soon as we stop using some dynamically allocated memory, we should
free it immediately.
Let's allocate memory for 100 int
s at runtime:
int main(int argc, char** argv) { int *p_i; printf("Trying to allocate memory for 100 ints.\n"); // Allocation of 100 times the size of int p_i = (int *) malloc(sizeof(int) * 100); // Checking whether the allocation was successful if (p_i == NULL) { printf("Not enough memory.\n"); exit(1); } // Freeing the memory printf("Freeing the memory.\n"); free(p_i); p_i = NULL; // We null the pointer just to be sure return (EXIT_SUCCESS); }
The output:
c_malloc
Trying to allocate memory for 100 ints.
Freeing the memory.
So far, we don't know how to access the individual int
s in the
memory yet. We'll explain everything next time. The exit()
function
terminates our application. As a parameter, we pass an error code which is
non-zero if the program didn't finish properly.
Common mistakes when working with pointers
Working with pointers is quite dangerous, as no one is watching us, programmers. Making a mistake in a program is very simple, one doesn't even have to be a beginner. Let's mention a few points that we should be careful about when working with pointers.
- Not freeing memory - If we forget to free some memory once, nothing will happen in principle. The problem is when we forget to free memory in a function which is called multiple times during the program's runtime. The worst situation is when we forget to free memory in a loop. We will, of course, run out of memory soon or later if we make this error. The application will fall and the user will lose their data and prefer to pay the rival company for a functional application
- Exceeding memory boundaries - Similarly to arrays, pointers don't keep track of what we store in their memory. If we store something larger than the amount of space we reserved, we'll break into the memory of another part of the application. This error may appear anywhere, and we'll be probably looking for it for a long time. This is because the error is not logically related to the location in the program where the memory overflows. Any part of the application can suddenly broke because the memory overflew to it
- Working with already freed memory - It can happen that we
free some memory and then try to write something to that address again. At that
point, however, we write to memory that doesn't belong to us. See the previous
point for aftermaths. Therefore, it's a good practice to store the
NULL
value to pointers to freed memory to avoid this error
In the next lesson, Pointer arithmetic in the C language, we'll learn pointer arithmetic. We'll find out that pointers in the C language are even closer to arrays than we thought.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 2x (70.98 kB)
Application includes source codes in language c