Understanding Memory Initialization in C
Memory is a vital aspect of programming, regardless of what you’re building. Understanding manual memory management is key for performance-critical systems, embedded development, and debugging low-level bugs. There are three key high level aspects you need to understand:
- Allocation
- Usage
- Deallocation
When you allocate memory on heap, you’re requesting a block of memory from the operating system. This memory isn’t automatically initialized, which means it contains whatever values were previously stored there.
// allocate 8 bytes of memory
char* buffer = (char*)malloc(8);
After allocation, you should initialize your memory to known values to avoid unpredictable behavior.
// initialize all bytes to zero
memset(buffer, 0, 8);
Or we could use calloc
, which will allocate memory on heap like malloc
but additionally it will initialize the values
// allocate and zero-initialize 8 bytes
char* buffer = (char*)calloc(8, sizeof(char));
Forgetting to initialize memory
char* data = (char*)malloc(4);
// 'data' contains uninitialized (garbage) values
printf("First byte: %d\n", data[0]);
Consider following more practical example
Imagine that we have simple sequence of frames that makes animation, and we want to display it in our game
typedef struct AnimationPlayerState {
int speed;
int index;
int length;
} AnimationPlayerState;
#define ANIM_FRAME_COUNT 8
#define MAX_STR_LEN 32
const char frame_names[ANIM_FRAME_COUNT][MAX_STR_LEN] = {
"player-jump-frame-0",
"player-jump-frame-1",
"player-jump-frame-2",
"player-jump-frame-3",
"player-jump-frame-4",
"player-jump-frame-5",
"player-jump-frame-6",
"player-jump-frame-7",
};
Then for this example sake, let’s allocate AnimationPlayerState
on heap
AnimationPlayerState* anim_state = (...*)malloc(sizeof(AnimationPlayerState));
if (anim_state == NULL) {
// We were not given piece of memory to work with.
}
At this point, memory is allocated, but fields like index
and length
are left uninitialized
Problem arises when we try to use those members
void Update(AnimationPlayerState* anim_state)
{
// attempting to access a frame using an uninitialized index
// potential out-of-bounds access
const char* frame = frame_names[anim_state->index++];
// 'index' was not initialized
// it could be any value, negative, very large, or random.
// accessing frame_names out of bounds results in UB
// and possibly crashing the program.
}
You can avoid this by either initializing the struct manually or using calloc
as we mentioned above:
But this would still require additional adjustments for members for their specific roles
AnimationPlayerState* anim_state = (...*)calloc(1, sizeof(AnimationPlayerState));
This zeroes all fields, but you may still need to explicitly set meaningful defaults:
anim_state->speed = 1;
anim_state->length = ANIM_FRAME_COUNT;
Or alternatively, initialize it manually
Preferably if you have members that have different responsibilities, like here
AnimationPlayerState* anim_state = (...*)malloc(sizeof(AnimationPlayerState));
anim_state->speed = 1;
anim_state->index = 0;
anim_state->length = ANIM_FRAME_COUNT;
Interactive Memory Visualization
Below is an interactive visualization that demonstrates the different states of memory throughout its lifecycle. Click
on the visualization to cycle through the states and see how memory changes: