Introduction
In our previous lesson, we established the fundamental difference between stack and heap memory. We learned that while the stack provides fast, automatic memory management for function-local data, its rigid, scope-based lifetime prevents us from creating data that needs to persist beyond a single function call. This is where the heap comes in, offering a flexible, manually-managed pool of memory for just such tasks.
Today, we move from the "why" to the "how." This lesson will equip you with the essential tools for controlling heap memory: the malloc() and free() functions. Mastering these functions is a rite of passage for any C programmer and is the key to unlocking truly dynamic programs. We will learn the precise syntax for requesting memory, the critical importance of releasing it, and the common errors to avoid. This skill is the direct prerequisite for building the flexible inventory system our game project requires.
Allocating Memory with malloc()
The primary function for requesting memory from the heap is malloc(), which stands for "memory allocate". It is declared in the <stdlib.h> header file. Its function prototype looks like this:
void *malloc(size_t size);
Let's break this down:
size_t size: This is the single argumentmalloc()takes, representing the number of bytes you wish to allocate.size_tis an unsigned integer type specifically designed to hold the size of any object. A common and robust practice is to use thesizeofoperator to determine the required number of bytes, which makes your code portable across different systems where data type sizes might vary.void *(return value): If successful,malloc()returns a "void pointer" to the first byte of the allocated memory block. Avoid *is a generic pointer that can point to any type of data, but it cannot be dereferenced directly. You must first cast it to a specific pointer type (likeint *orstruct Player *) to tell the compiler how to interpret the memory it points to.
The following video provides an excellent visual demonstration of this process.
Pointers and dynamic memory - stack vs heap
This video from mycodeschool clearly explains the mechanics of malloc().
Please watch the segment from this explanation. It shows how to call malloc() with sizeof(int), discusses the void * return type, and demonstrates the necessary typecasting to assign the address to an integer pointer.
Handling Allocation Failures
What happens if the system cannot fulfill your memory request? For example, you might ask for more memory than is available. In this case, malloc() will fail and return NULL. A NULL pointer is a special pointer that doesn't point to any valid memory location.
It is absolutely critical to check the return value of malloc() for NULL before you attempt to use the pointer. Accessing a NULL pointer results in undefined behavior and will almost certainly crash your program.
Here is a complete, robust example of allocating a single integer on the heap:

Notice the if (p) check. This is a concise way of writing if (p != NULL). Only if the pointer p is not NULL do we proceed to use the memory it points to.
Deallocating Memory with free()
The heap's flexibility comes with a crucial responsibility: you must manually return memory to the system when you are finished with it. If you don't, your program will suffer from a memory leak. A memory leak occurs when a program allocates memory but loses all pointers to it, rendering that memory unusable for the remainder of the program's execution. In a long-running program, like a game server or our text adventure, accumulating leaks can eventually consume all available memory and crash the system.
The function to release memory is free(), also in <stdlib.h>:
void free(void *ptr);
You pass free() the exact same pointer that malloc() returned. This tells the memory allocator that the block of memory pointed to by ptr is no longer in use and can be made available for future allocations.
Let's continue with the video to see free() in action.
Pointers and dynamic memory - stack vs heap
This next segment demonstrates the use of free() and explains its importance.
Watch from this point. The narrator emphasizes that it is the programmer's responsibility to clear memory from the heap to prevent unnecessary consumption of resources.
The fundamental rule of manual memory management is: for every successful call to malloc(), there must eventually be a corresponding call to free().
A Practical Use Case: Dynamic Arrays
Allocating single variables is useful, but the real power of malloc() shines when allocating arrays whose size is not known at compile time. For our game, imagine needing an inventory that can hold a variable number of items.
To allocate an array of, say, 20 integers, you would calculate the total bytes needed and pass that to malloc():
int *arr = (int *)malloc(20 * sizeof(int));
if (arr == NULL) {
// Handle allocation failure
perror("Failed to allocate memory for array");
exit(1);
}
// Now you can use 'arr' just like a normal array
for (int i = 0; i < 20; i++) {
arr[i] = i * 10;
}
printf("The 5th element is: %d\n", arr[4]); // Prints "The 5th element is: 40"
// When you are done with the array...
free(arr);
The video you watched earlier concludes with this exact technique.
Pointers and dynamic memory - stack vs heap
This final clip from mycodeschool shows how to use malloc() to allocate an array.
Watch the short segment from here. It shows how the total size is calculated (count * sizeof(type)) and that the resulting pointer can be used as the base address of an array.
Common Pitfalls and Best Practices
Manual memory management is powerful, but "with great power comes great responsibility." Simple mistakes can lead to baffling bugs and security vulnerabilities. Understanding these common errors is as important as knowing the syntax of malloc() and free().
The following reading provides a detailed look at these heap-related errors.
Dynamic Memory With malloc and free in C
This article by Alexander Obregon details the allocation functions and the errors that can arise from their misuse.
First, read the section "Allocation Functions". This will serve as a textual review of malloc and free, and it also introduces calloc and realloc, which we will cover later. Pay close attention to the free function description and the suggestion to set the pointer to NULL after freeing. Next, read the section titled "Heap Memory Errors". Focus on the descriptions of Memory Leaks, Double Frees, and Use-after-free bugs. This is critical for developing a defensive programming mindset.
To summarize the key errors:
- Memory Leak: Allocating memory with
malloc()but failing to release it withfree(). - Use-After-Free: Using a pointer after the memory it points to has been deallocated with
free(). The memory might be re-assigned to another part of your program, leading to data corruption. - Double Free: Calling
free()more than once on the same pointer. This can corrupt the allocator's internal data structures, leading to unpredictable crashes.
A simple but effective best practice to mitigate some of these issues is to set your pointer to NULL immediately after freeing it:
free(p);
p = NULL;
This turns a potential use-after-free bug into a guaranteed, and more easily debuggable, NULL pointer crash. It also makes calling free(p) again completely harmless, as free(NULL) is defined as a do-nothing operation.
A Look Under the Hood
Given your engineering background, you might appreciate a conceptual model of what the memory allocator is actually doing. malloc is not magic; it's a runtime library function that acts as a bookkeeper for the heap.

When you call malloc(), the allocator searches its internal records (often called a "free list") for a block of unused memory that is large enough to satisfy your request. It then marks that block as "in use" and returns a pointer to you. When you call free(), it marks the block as "available" again. This process can lead to a phenomenon called fragmentation, where the available heap memory is broken up into many small, non-contiguous chunks.
This article provides a superb, high-level explanation of this process.
Understanding Memory Management, Part 1: C
This article from the blog "Educated Guesswork" gives an intuitive explanation of how a memory allocator might work internally.
Read the section titled "How malloc() works". Focus on understanding the concepts of bookkeeping, the creation of "holes" when memory is freed, and fragmentation. The explanation of how metadata (like the block size) is often stored in a header is particularly insightful.
Understanding this model clarifies why malloc() is slower than stack allocation (it has to search and do bookkeeping) and why errors like double-freeing or freeing an invalid pointer are so dangerous—they corrupt the allocator's critical bookkeeping data.
Conclusion
You've now taken your first and most important step into manual memory management in C. You've learned not just how to use malloc() and free(), but also why it's so important to use them correctly.
Key Takeaways:
malloc()requests a specific number of bytes from the heap and returns avoid *on success, orNULLon failure.- Always check for
NULLaftermalloc()to handle allocation failures gracefully. free()returns a block of memory to the heap, making it available for reuse.- The cardinal rule is: one
free()for every successfulmalloc()to prevent memory leaks. - Be vigilant against common errors like use-after-free and double-free. Setting a pointer to
NULLafter freeing it is a strong defensive practice. - The allocator is a bookkeeper for the heap, and corrupting its data with improper
free()calls leads to undefined behavior.
We've discussed memory leaks in theory, but how do we find them in a real program? In our next lesson, we will introduce Valgrind, a professional-grade tool that can automatically detect memory leaks and other memory-related errors, turning an otherwise invisible bug into a clear, actionable report.