class: center, middle ### Secure Computer Architecture and Systems *** # Dynamic Memory Allocation in C ??? - Hi everyone, welcome to this quick video about dynamic memory allocation in C --- # Motivation - So far we covered programs using only **static** memory allocation - I.e. the allocation size is known at compile time - How to manage situations in which **the allocation size is not known at compile time?** ??? - So far we looked at programs in which all the amount of memory needed for their data and code was known at compile time - As a result the compiler was handling all memory allocation automatically - This is called static memory allocation - However there are situation where we need to reserve some memory which size is not known until runtime -- .leftcol[ ```c void process_array(int size) { int arr[size]; // variable-size array, // not a great solution for (int i = 0; i < size; i++){ arr[i] = i * i; printf("%d\n", arr[i]); } } ``` ] .rightcol[ ```c int main() { int n; printf("Enter the size of the array: "); scanf("%d", &n); if (n > 0) process_array(n); printf("all good\n"); return 0; } ``` .codelink[
`04-c-dynamic-memory-allocation/variable-size-array.c`
] ] ??? - Here is an example of a program that ask the user for a number with the `scanf` function - That number is then passed to a function that allocates an array which size is based on that number - The size will only be known at runtime because it depends on the number entered by the user - What we have here is a variable-sized array, which is generally a poor practice in C - As a local variable it is stored in an area of the address space called the stack, and the stack has a very small size, just a few megabytes - So as a result if the user enters a number that is too large, the stack will overflow and the program will crash - We need a better solution --- # `malloc` - `malloc` lets the programmer allocate a given amount of bytes **at runtime**, i.e. dynamically: ```c void *malloc(size_t size); ``` - Takes as parameter the amount of bytes to allocate and returns: - A pointer to the allocated space if the allocation was successful - `NULL` if it failed - `malloc` never guarantees that an allocation will succeed - Depends on the amount of free memory available on the machine at the time of the allocation - Return generic pointer (`void *`), can be cast to any other pointer type - I.e. `malloc` allocates memory that can hold any type ??? - To allocate a variable and pontentially large amount of memory at runtime, in C we must use the `malloc` function - It takes as parameter `size`, the amount of bytes to allocate - It allocates a contiguous area of memory in the address space (we call that a buffer), and returns a pointer to the first byte of that area, or `NULL` if the allocation failed - One thing with `malloc` is that although for allocation requests of reasonable sizes it will mostly succeed, you can never be sure of the success or failure of the allocation - Indeed the system may be running low on memory and unable to satisfy certain calls to `malloc` - As a result it is important to always check that the return value of `malloc` is not `NULL` before starting to use the allocated memory - If `malloc` returns NULL you also need to take appropriate action, for example exit the program with an error message - `malloc` also returns a `void *` pointer, it's a generic pointer that can be transformed into a pointer of any other type, we call that a cast - As a result `malloc` can allocate memory that can hold any type of data --- # `malloc` and `free` - **Memory allocated with malloc needs to be released explicitly** by the programmer using the `free` function - Takes as parameter a pointer to the area to release ??? - Memory allocated manually by the programmer with `malloc` must also be released manually when it is no longer needed - This is done with the `free` function, which takes as parameter the pointer that was returned by `malloc`, in other words the first byte of the memory area to release -- Our previous example with dynamic memory allocation: ```c void process_array(int size) { int *arr = (int *)malloc(size * sizeof(int)); // Allocate an area of memory large enough // to contain n integers if(arr == NULL) { /* ALWAYS check malloc's return value, if it fails we just exit */ printf("ERROR: cannot allocate memory\n"); exit(-1); } for (int i = 0; i < size; i++) arr[i] = i * i; free(arr); /* release memory with free */ } ``` .codelink[
`04-c-dynamic-memory-allocation/malloc.c`
] ??? - If we rework our previous example to use dynamic memory allocation with `malloc` and `free`, you can see that the function allocating the array starts by calling malloc - It requests an amount of space to store size integers, so the parameter passed to malloc is `n * sizeof(int)` - The `(int *)` before malloc is our cast: we transform the void pointer it returns into a pointer of int, which as you recall from the previous video is an array of integers - We then need to check that the allocation suceeded - If it failed we print an error message and exit - If it succeeded, we can iterate over the array normally and populate it - Once we are done, we can release the memory using free --- # Memory Leaks - Don't forget to free memory! ```c void process_array(int size) { int *arr = (int *)malloc(size * sizeof(int)); // Allocate an area of memory large enough // to contain n integers if(arr == NULL) { /* ALWAYS check malloc's return value, if it fails we just exit */ printf("ERROR: cannot allocate memory\n"); exit(-1); } for (int i = 0; i < size; i++) arr[i] = i * i; // No free ! At that stage the pointer arr is gone so the memory will never be freed! // It's a leak of size * sizeof(int) bytes } ``` .codelink[
`04-c-dynamic-memory-allocation/my-leaky-program.c`
] ??? - It's important that you do not forget to release memory allocated with `malloc` when it is no longer needed - If you don't we say that your program has a memory leak - Leaks are a security issue: the ycan be exploited by an attacker to crash your program or starve the machine of resources - See our previous example here, in which I removed the free operation: this program is incorrect and allocates memory that is never freed: it leaks memory --- # Memory Leaks - **Valgrind** can help identify leaks among other memory errors ```bash $ gcc -g my-leaky-program.c -o my-leaky-program # embed debug info in the program (-g) $ valgrind --leak-check=full ./my-leaky-program # invoke valgrind # ... Enter the size of the array: 100 ==144325== ==144325== HEAP SUMMARY: ==144325== in use at exit: 400 bytes in 1 blocks ==144325== total heap usage: 3 allocs, 2 frees, 2,448 bytes allocated ==144325== ==144325== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==144325== at 0x48407B4: malloc (vg_replace_malloc.c:381) ==144325== by 0x109194: process_array (my-leaky-program.c:5) ==144325== by 0x109235: main (my-leaky-program.c:25) ==144325== ==144325== LEAK SUMMARY: ==144325== definitely lost: 400 bytes in 1 blocks ==144325== indirectly lost: 0 bytes in 0 blocks ==144325== possibly lost: 0 bytes in 0 blocks ==144325== still reachable: 0 bytes in 0 blocks ==144325== suppressed: 0 bytes in 0 blocks ``` ??? - There is a very useful tool named Valgrind that can detect memory leaks - When we run our leaky program under valgrind, it reports 400 bytes of memory leaked, and gives some information about where the leak comes from so it can be fixed - Please make sure to use valgrind to check for leaks on all the C code your produce as part of this unit - Leaks are a bug and a security issue, and your code should be free from them --- # Summary - **Dynamic memory allocation** needed when the size to allocate is not known at compile time - Achieved in C with `malloc` and `free` - Don't forget to: - Check `malloc` return value - Release memory with `free` to avoid leaks ??? - And that's it for dynamic memory allocation - It is needed when the size to allocate is not known at compile time - It's done with `malloc`, and the memory allocated this way needs to be released with free - Don't forget 2 important things: - Always check `malloc` return, you cna never tell for sure if it will be NULL or not - Also, always release memory allocated with `malloc` using free to avoid leaks