Dynamic memory allocation
When variables and functions are created they go in the computer's memory. At this introductory level we ignore how memory is managed. We are assuming that it is correctly allocated and deallocated by the operational system. There is a whole class of algorithms that deal with memory management that we won't go in detail at this time.
There are many natural questions that arise when we think about how the operational system handles memory: "Where in the memory are variables stored?", "Where in the memory are functions stored?", "Can part of a program invade the memory used by other programs?", "Can a variable be overwritten by another in memory?", "What if the program requires more memory than the quantity that is physically available?", "Does the operation to allocate and the operation to deallocate cost something in terms of processing or memory itself?", etc. Those questions need understanding about how operational systems work to be fully answered.
For this level we are going to concentrate on dynamic memory allocation of arrays and matrices. Back in the chapter about arrays and matrices we learned that C doesn't allow the index of an array to be a variable. It must be a constant that can't be changed during program's runtime. To have arrays of variable size we have to use special functions that deal with dynamic memory allocation. The reason for that is, when we declare variables or arrays, the memory is allocated as soon as the program runs. It's static, fixed size.
Errors of logic:
- Allocate memory and lose track of the reference (the pointer) to that memory chunk, which renders whatever data that was stored there inaccessible;
- Allocate memory and forget to free it after it's no longer needed, that's what we call a "memory leak".
The examples require clear understanding about functions and pointers
- Dynamically allocating an array:
/* Declaring a pointer and making it point at a memory block with the size of 100 integers */
int *p;
p = (int *) malloc(100*sizeof(int));
sizeof operator: it gets the size, in bytes, of what comes after it. It might look as if it's a function, but the parenthesis there are due to the operator's precedence. In fact, in the above example, we wouldn't need them, though it makes code better to read.
function malloc: its argument it's the number of bytes that are going to be allocated. In the above example, the size of 100 integers. malloc is a function that returns a pointer to the beginning of the allocated memory block. In the code above we are assigning that pointer to the pointer p.
Type cast: a question arises here "malloc returns a pointer of which type?". We have a cast operation (int *) to ensure that we are assigning a pointer to int to another pointer to int. This is not always required, it depends on the compiler used. Note the parenthesis, if we forget the parenthesis the compiler will read that malloc itself is a pointer.
Overhead: there is an extra memory that is required for malloc to work, that extra memory is used by the OS to manage the memory allocated during program's runtime. In general it's better to have fewer malloc calls of larger chunks of memory than more calls of smaller chunks.
Note: there is no way to make a program guess the amount of memory required if the inputted data can vary from very small to very large. Suppose that the array is going to store the characters of a person's name. We can either force the user to input the number of letters in his name, plus blank spaces, or make malloc allocate an array which is large enough for any possible name.