Skip to content

Memory Management

When you are getting into kernel development or any kind of more advanced programming with a systems level language you will need to know how pointers work and how to properly get memory with malloc and free.

Memory Management is very simple on the surface but can be a cause of many crashes and bugs like memory leaks if done improperly.

Memory Management TLDR

  • Allocate memory and get a pointer to it with malloc()
  • Free memory you got with malloc() when you are done with it using free()
  • Do not use memory after calling free() on it
  • Do not call free() on memory twice
  • A pointer is a variable that references another variable

How to use malloc() and free()

When you want to get memory dynamically like any complex project will have to do at some point you have to use the functions malloc() and free()

malloc() will give you a void* pointer which is a pointer that refers to a block of memory that has no type and you have to cast it to the correct one

After you are done using that memory you call free() on that pointer so that memory can then be reused later because if not the system will eventualy run out of memory

Example:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    void* malloced_memory = malloc(4096); // Allocate 4kb of memory
    memset(malloced_memory, '0', 4096); // Clear the malloced memory
    char* string = (char*)malloced_memory; // Cast to a string (null terminated char* in C)

    strcpy(string, "Hello, Malloc & Free"); // Put some data in there (Do not overflow it or it can and will cause severe issues)

    printf("%s", string); // Print the string

    free(malloced_memory); // Free the memory

    return 0;
}

What is a pointer

A pointer is a variable that references another variable which lets you pass them around and let things work with the same object.

Normally when you pass something to a function via an argument it gives a copy of it.
But if you use a pointer to pass it the function will operate on the original object.

Example:

#include <stdio.h>

int no_ptr(int x) {
    x++;
    return x;
}

int ptr(int* x) { // When declaring a pointer variable or having it as an argument in a function you add a * after the variable type as in here
    *x = *x + 1; // You also use the * operator to dereference it and use the actual value it points to here
    return *x;
}

int main() {
    int x = 3;
    printf("Main X = %x\n", x); // X is 3
    printf("No Pointer Func Returns: %x\n", no_ptr(x)); // Returns 4
    printf("Main X = %x\n", x); // X is still 3
    printf("Pointer Func Returns: %x\n", ptr(&x)); // Returns 4 // use the & operator before a variable to convert to a pointer
    printf("Main X = %x\n", x); // X is now equal to 4 because the ptr function has the reference to the variable and modified it directly

    return 0;
}
This is most commonly used to pass big structures or objects to other things for performance or needing multiple things to work with the same memory (Multithreading needs extra considerations and making sure they do not contend using stuff like a spinlock or mutex)

Function Pointers

A pointer can point to a function which lets you assign a function to a variable and then call it without knowing exactly what function it is as long as the inputs are the same.

This can be combined with more advanced data structures and dynamically allocated memory with malloc() and free() to say have a list of objects with functions you can call on them that do different things similar to OOP without the language directly supporting it.

Example:

#include <stdio.h>
#include <stdlib.h>

struct object_node* objects = NULL;

struct object {
    int (*update)(int x);
};

struct object_node {
    struct object* obj;
    struct object_node* next;
};

int add_object(struct object* obj) {
    struct object_node* new_obj = malloc(sizeof(struct object_node));
    new_obj->obj = obj;
    new_obj->next = objects;
    objects = new_obj;

    return 0;
}

int del_object(struct object* obj) {
    struct object_node* current = objects;
    struct object_node* prev = NULL;

    while (current != NULL) {
        if (current->obj == obj) {
            if (prev == NULL) {
                objects = current->next;
            } else {
                prev->next = current->next;
            }
            free(current->obj);
            free(current);
            return 0;
        }
        prev = current;
        current = current->next;
    }
    return -1;
}

void upd_objects(int x) {
    struct object_node* current = objects;
    while (current != NULL) {
        if (current->obj && current->obj->update) {
            current->obj->update(x);
        }
        current = current->next;
    }
}

int cat(int x) {
    printf("Meow %d\n", x);
    return 0;
}

int dog(int x) {
    printf("Bark %d\n", x);
    return 0;
}

int main() {
    // Add some objects
    struct object* cat1 = malloc(sizeof(struct object));
    cat1->update = cat;

    struct object* dog1 = malloc(sizeof(struct object));
    dog1->update = dog;

    struct object* cat2 = malloc(sizeof(struct object));
    cat2->update = cat;

    add_object(cat1);
    add_object(dog1);
    add_object(cat2);

    // Run the update() function on them
    upd_objects(1);

    // Delete one and update again and it wont run that one
    del_object(dog1);
    upd_objects(2);

    // Free memory before leaving
    del_object(cat1);
    del_object(cat2);

    return 0;
}