Skip to content

2.2 Memory Allocation

In this note, we talk about memory allocation using calls to malloc, calloc, and realloc and also talking about freeing this memory allocation using free. First, we will talk about pointers and addresses. Get back to toc CS61C or 2. C Programming.

Introduction to Pointers and Addresses

These are probably not new terms to you, but its use is probably new. Like in CS61A, we used pointers to represent the location where things like lists, objects, functions are stored. In Python, when we did something like

x = [1, 2, 3]

we create a box and pointer diagram where x really contains the address of the list in memory, but then it is visually represented as a pointer. Pointers and addresses conceptually are just the same idea in C, but now we will have to explicitly use them.

I can declare a pointer type with the asterisk *. For example, a pointer to an integer can be declared as

int* pint = 5;

and it can be accessed by dereferencing the pointer, also using the asterisk:

int* pint = 5;
*pint = *pint + 1; # Says that the dereference of pint plus 1 is assigned to the dereference of pint. In other words, the value 6 is stores at where pint points to. 

You can specify the address of something with the ampersand symbol &. For example, if I declare an integer x, I can assign the address of x to be assigned to a integer pointer y:

int x = 1;
int* y = &x;
int t = *y + x; # t stores the value 2.

You can kind of see it be a little bit confusing, using both the asterisk as a sort of variable type and dereferencing operator.

Allocation

Let's take a sidestep to Java and Python really quickly. Remember when you make objects, you need to call a constructor. What really happens is that Java or Python automatically creates the necessary space based on the type you tell it to be, and then gives you a pointer to the object to which then you can assign to a variable name. C makes this process a bit more explicit with the malloc, calloc, and realloc.

Let's look at the syntax of these allocation methods: - malloc: returns a pointer to the allocated memory or NULL if the request fails. It requries the size of the memory block. It's syntax is void *malloc(size_t size). All memory here is uninitialized and unknown, but you can assign parts of this allocated memory to make the memory have defined behavior later. - calloc: returns a pointer to the allocated memory or NULL if the request fails. It differs from malloc in that calloc will set the allocated memory to zero. Its syntax is also a little bit different: void *calloc(size_t nitems, size_t size) which specifies how many items of what size you want. - realloc: Attempts to resize the memory block pointed to by ptr that was previously allocated with a call to malloc or calloc, or NULL if this reallocation request fails.. Its syntax is void *realloc(void *ptr, size_t size).

Now let's get used these requests to allocate memory. I think the main point of confusion right now if you haven't seen it before is the size field of the function. Now, you'll learn so much about sizes of types throughout this class, and more about it in 3. Memory, but as of now you just need to know a few sizes: - An int is 4 bytes of memory - A short is 2 bytes of memory - A long is 8 bytes of memory - A char is 1 byte of memory - Any pointer's size depends on the system. You'll face a 32-bit system more often, which designates a pointer to have a size of 4 bytes, but you may see 64-bit systems, which designates 8 bytes for a pointer.

We will also learn more types along the way, such as sizes of more complicated data structures in 4. More C Programming.

So, if you wanted to allocate a piece of memory that can contain 2 integer types, you might want to try

int *pint = malloc(8); 

# alternatively, we can use the sizeof(<type>) function, which tells you the size of a type:

int *pint2 = malloc(2 * sizeof(int));

Now we have two pointers of allocated memory. We can assign values to it just like an array. The relative spacing of the allocated memory is automatically known when you index. For int pointers every index has 4-byte spacing, and for char pointers, every index has 1-byte spacing. For example, we can do

pint[1] = 2;

# Another way to write memory is through dereferencing. This syntax is a bit old-fashioned, but it exists

*(pint + 1) = 3; # assigns 3 to the index 1 of this allocated memory block. 

We will take a deep dive into pointers and arrays in the next section 2.3 Pointers and Arrays, but right now an introduction is good to know.

Memory Leaks and Free

What is different from this type of memory that has been allocated is that we have to tell our system to free memory. If we do not free the memory that has been allocated, you'll have memory leaks. Memory leaks are gradual deterioration of system performance that occurs when there is a failure to free memory segments allocated to RAM. Maybe you've heard of the idea of restarting your computer to maintain performance. This is exactly the reason why: you need to restart your system so that all the memory leaks get fixed when your system boots up.

What is unfortunate is that C will not tell you if you are leaking memory. You need to use other programs like Valgrind to tell you if you are leaking memory.

So, to free memory, you simply have to call free over the variable name.

int *ptr = calloc(4, sizeof(int)); #allocates 4 integers to ptr, each set to 0. 

free(ptr) # frees the memory allocated to ptr


int *nofree = malloc(3 * sizeof(char));

# we might do stuff with nofree, but if we do not free it by the end of the program, it is a memory leak (like I am going to do right now). 

When you do stuff like pointers to pointer (yes those are things, such as matrices or lists of pointers), you cannot simply free the list and expect it to free everything that is contained in it. You have to free every single pointer allocated, so that means going through your matrix, and freeing every pointer to integer arrays, then free your matrix pointer. Again, C will not tell you if you leaked memory, but if you don't free your memory, You cannot simply free memory ever once your program finishes running, and so your system will slow down if you leak too much. You can restart your system to free this memory.

Summary

So now, you are getting into the intricacies of C programming. It's definitely annoying that C won't tell you if you are wrong all the time, but that is what low-level programming is all about. When performance matters, you wish you can have control of everything, from control to allocation and freeing memory. Languages like Java and Python have garbage collectors that do the freeing of memory for you, but it is at the expense of being a slower language.

Go to next section: 2.3 Pointers and Arrays