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 usingfree() - 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;
}
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;
}