Skip to content

C Memory Management

C programs use memory divided into several segments: the text segment (code), data segment (global and static variables), stack (local variables, function calls), and heap (dynamic memory). This chapter focuses on dynamic memory management on the heap.

Dynamic memory allocation allows programs to request memory at runtime as needed, rather than allocating fixed amounts at compile time. This is crucial when the amount of memory required is unknown beforehand or varies significantly. The standard library <stdlib.h> provides key functions for this purpose.

S.N.FunctionDescription
1void *malloc(size_t size);Allocates a block of size bytes of uninitialized memory from the heap. Returns a void* pointer to the start of the block, or NULL if allocation fails.
2void *calloc(size_t num, size_t size);Allocates memory for an array of num elements, each of size bytes. The allocated memory is initialized to zero. Returns a void* pointer, or NULL on failure.
3void *realloc(void *ptr, size_t new_size);Changes the size of the memory block pointed to by ptr to new_size bytes. It might move the block to a new location. Returns a pointer to the potentially relocated block, or NULL if resizing fails (the original block remains allocated in this case). If ptr is NULL, it behaves like malloc(new_size). If new_size is 0, it might free the memory and return NULL (behavior can vary).
4void free(void *ptr);Deallocates the memory block previously allocated by malloc, calloc, or realloc and pointed to by ptr. Passing a NULL pointer to free is safe and does nothing. Crucially, allocated memory must be freed when no longer needed to prevent memory leaks.

Note: size_t is an unsigned integer type defined in &lt;stdlib.h&gt; (and other headers) suitable for representing sizes of objects in memory.

If you know the exact size needed at compile time, you can use arrays on the stack or static memory. For example, storing a short name:

char short_name[50]; // Max 49 characters + null terminator

However, consider storing a user-provided description of unknown length. We can’t define a fixed-size array that’s guaranteed to be large enough without potentially wasting huge amounts of memory. Dynamic allocation solves this. We use a pointer and allocate memory on the heap using malloc:

#include <stdio.h>
#include <stdlib.h> // For malloc, free, fprintf, stderr
#include <string.h> // For strcpy
int main(void) { // Use standard main signature
char name[100]; // Stack allocated buffer for a known max size
char *description = NULL; // Pointer for dynamically allocated memory, initialize to NULL
strcpy(name, "Zara Ali");
// Allocate memory dynamically for the description
// Let's say we estimate needing 200 chars initially
size_t description_size = 200;
description = malloc(description_size * sizeof(char)); // sizeof(char) is always 1, but good practice
// *** ALWAYS check the return value of malloc! ***
if (description == NULL) {
fprintf(stderr, "Error: Unable to allocate required memory for description.\n");
return 1; // Indicate failure
}
// Memory allocated successfully, now we can use it
strcpy(description, "Zara Ali is a student."); // Ensure string fits allocated size!
printf("Name = %s\n", name);
printf("Description: %s\n", description);
// *** IMPORTANT: Free the allocated memory when done ***
free(description);
description = NULL; // Good practice to NULL the pointer after freeing
return 0; // Indicate success
}

When compiled and executed, this might produce:

Name = Zara Ali
Description: Zara Ali is a student.

Using calloc is similar but initializes the memory to zero:

// Allocate and zero-initialize memory for 200 characters
description = calloc(description_size, sizeof(char));
if (description == NULL) { /* ... error handling ... */ }
// 'description' now points to 200 bytes of zero

Dynamic allocation gives you control over memory lifetime and size at runtime, unlike fixed-size arrays.

While the operating system reclaims memory when your program exits, it’s crucial best practice to explicitly release memory using free() as soon as it’s no longer needed. Failing to do so leads to memory leaks, where your program consumes more and more memory over time, potentially crashing itself or the system.

Sometimes, the initial allocated size isn’t sufficient. You can resize a previously allocated block using realloc(). Let’s modify the previous example to store a longer description:

#include <stdio.h>
#include <stdlib.h> // For malloc, realloc, free, fprintf, stderr
#include <string.h> // For strcpy, strcat
int main(void) {
char name[100];
char *description = NULL;
strcpy(name, "Zara Ali");
// Initial allocation (e.g., for a short description)
size_t initial_size = 30;
description = malloc(initial_size * sizeof(char));
if (description == NULL) {
fprintf(stderr, "Error: Initial memory allocation failed.\n");
return 1;
}
strcpy(description, "Zara is a student."); // Fits initial size
printf("Initial Description: %s\n", description);
// Now, suppose we need to add more information
const char *additional_info = " She studies Computer Science.";
size_t current_len = strlen(description);
size_t additional_len = strlen(additional_info);
size_t required_size = current_len + additional_len + 1; // +1 for null terminator
// Resize the memory block if needed
if (required_size > initial_size) {
char *new_description = realloc(description, required_size);
// *** ALWAYS check the return value of realloc! ***
if (new_description == NULL) {
fprintf(stderr, "Error: Memory reallocation failed.\n");
// IMPORTANT: 'description' still points to the old block!
// We must still free the original block before exiting.
free(description);
return 1;
}
// Reallocation successful, update the pointer
description = new_description;
}
// Now we have enough space, append the additional info
strcat(description, additional_info); // strcat is safe now
printf("Name = %s\n", name);
printf("Full Description: %s\n", description);
// *** Release the final allocated memory ***
free(description);
description = NULL;
return 0;
}

Executing this code would output:

Initial Description: Zara is a student.
Name = Zara Ali
Full Description: Zara is a student. She studies Computer Science.

If you tried the strcat() without realloc() in the example above, you would likely cause a buffer overflow, writing past the allocated memory boundary. This leads to undefined behavior, often crashing the program or corrupting data, and can be a serious security vulnerability.

  • Memory Leaks: Forgetting to free() allocated memory.
  • Dangling Pointers: Using a pointer after the memory it pointed to has been free()d.
  • Double Free: Calling free() twice on the same pointer.
  • Invalid Free: Calling free() on a pointer not returned by malloc/calloc/realloc (e.g., a stack variable address).
  • Not Checking malloc/calloc/realloc Return: Assuming allocation succeeded without checking for NULL.

Tools like Valgrind (on Linux/macOS) can help detect memory errors.