After practicing stdio.h and file I/O in lesson 12, it is time to “borrow just enough memory” during execution. Up to now we fixed array sizes at compile time, but user input length or file size is unknown ahead of time. malloc and free let programs request space on demand and release it later.
New Terms in This Post
- Heap: The memory region where a program can request arbitrary amounts while it runs
- Dynamic memory allocation: The process of reserving space at runtime with functions such as
malloc - size_t: The unsigned integer type used for expressing sizes and counts
- Memory leak: Memory that was allocated but never freed and can no longer be reached
Key Ideas
Study Notes
- Time required: 60–90 minutes
- Prerequisites: arrays, basic pointers,
stdio.hexperience- Goal: use the
mallocfamily to reserve space and terminate its lifetime explicitly withfree
Static arrays are hard to resize, so they struggle with unknown or growing inputs. You often cannot predict how many characters a user will type or how many scores live in a file. Dynamic memory solves that “size decided at runtime” problem. Each allocation should follow four steps:
- Request the needed number of bytes with
malloc/calloc/realloc. - Check the return value for
NULLto confirm success. - Use the pointer like an array or hand it to other functions.
- Free the space when done, ideally setting the pointer to
NULLafterward.
In this post we will:
- Compare static arrays with heap allocations
- Explore similarities and differences among
malloc,calloc, andrealloc - Explain why
freeis mandatory and how to detect leaks - Practice
NULLchecks - Grow a buffer on demand based on input length
Code Walkthrough
Comparing Stack Arrays and Heap Memory
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int fixed[3] = {1, 2, 3};
int *dynamic = malloc(sizeof(int) * 3);
if (dynamic == NULL) {
return 1;
}
for (int i = 0; i < 3; i++) {
dynamic[i] = (i + 1) * 10;
}
printf("fixed[1] = %d\n", fixed[1]);
printf("dynamic[1] = %d\n", dynamic[1]);
free(dynamic);
return 0;
}
fixed lives in stack memory with a compile-time length. dynamic borrows space from the heap during execution, and free(dynamic); releases it explicitly.
Requesting Space with malloc
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t count = 5;
int *scores = malloc(sizeof(int) * count);
if (scores == NULL) {
printf("Failed to allocate memory.\n");
return 1;
}
for (size_t i = 0; i < count; i++) {
scores[i] = (int)(i * 10);
}
for (size_t i = 0; i < count; i++) {
printf("scores[%zu] = %d\n", i, scores[i]);
}
free(scores);
scores = NULL;
return 0;
}
- Compute bytes as “number of elements × size of one element,” such as
sizeof(int) * count. size_tis the standard type for sizes: unsigned and naturally sized per platform.- After
free, assignNULLso accidental reuse stands out.
Getting Zero-Initialized Space with calloc
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t count = 4;
int *buffer = calloc(count, sizeof(int));
if (buffer == NULL) {
return 1;
}
for (size_t i = 0; i < count; i++) {
printf("buffer[%zu] = %d\n", i, buffer[i]);
}
free(buffer);
return 0;
}
calloc takes the element count and element size separately and fills the block with zeros. For beginners, the easiest way to think about it is this: malloc gives you uninitialized memory, while calloc gives you zeroed memory. If you want an integer array to start at 0, calloc is often the simpler choice.
Growing a Buffer with realloc
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
size_t capacity = 4;
size_t length = 0;
char *line = malloc(capacity);
if (line == NULL) {
return 1;
}
const char *source = "C-memory";
for (size_t i = 0; source[i] != '\0'; i++) {
if (length + 1 >= capacity) {
size_t new_capacity = capacity * 2;
char *temp = realloc(line, new_capacity);
if (temp == NULL) {
free(line);
return 1;
}
line = temp;
capacity = new_capacity;
}
line[length++] = source[i];
}
line[length] = '\0';
printf("line = %s (capacity = %zu)\n", line, capacity);
free(line);
return 0;
}
reallocresizes an existing block.- Capture the return value in a temporary pointer so you don’t lose the original on failure.
- If it succeeds, use only the new pointer; the old address may no longer be valid.
- The reallocated memory might move, so never keep old pointers around.
Always Think About Failure
Heap space is finite. When malloc fails it returns NULL, so treat this pattern as mandatory:
int *data = malloc(sizeof(int) * n);
if (data == NULL) {
// Log, notify the user, and exit safely
}
free is safe to call with NULL, so if you are not sure whether a pointer was freed, checking for NULL first is fine.
Building Habits for Detecting Leaks
Create a quick review checklist to avoid leaks:
- Decide whether the function that calls
mallocalso callsfree, or whether it returns the pointer and hands off ownership. - If the function has multiple
returnstatements, ensure each path frees what it owns. - When a struct holds dynamic arrays, include a corresponding cleanup function that frees the inner pointers.
Why It Matters
- Dynamic memory is essential for inputs whose size is determined at runtime: user text, file contents, network data, and more.
- Understanding
malloc/freesets the stage for pointer arithmetic, structs, and custom containers. - Tracking leaks trains you to manage other resources (files, sockets) just as carefully.
Practice in CodeSandbox
The sandbox below uses CodeSandbox's Universal starter. For C, the key learning loop is still compile and run in the terminal, so recreate the lesson code as a source file and repeat that cycle directly.
💬 댓글
이 글에 대한 의견을 남겨주세요