C-MEMORY MANAGEMENT & ALLOCATION

Dev Frank
7 min readJun 6, 2024

--

Creating a program that consumes excessive or unnecessary memory can lead to sluggish and inefficient performance.

In C, memory management falls squarely on your shoulders. Though it’s a complex responsibility, it empowers you to optimize your program’s performance. Understanding how to release memory when it’s no longer needed and using just enough for the task at hand is crucial.

In earlier sections, you gained insights into memory addresses and pointers, both pivotal in memory management. Pointers enable direct manipulation of memory, enhancing control over resource usage.

Exercise caution, however, as mishandling pointers can inadvertently corrupt data stored in other memory locations.

Take your computer’s memory like a big box of Legos. When you build something with Legos, you want to use just the right amount of pieces — not too many, not too few.

In programming, if your program uses too many Legos (memory), it can slow down and not work as well. In C, you’re the one responsible for managing these Legos. It’s like being in charge of deciding how many Legos to use and when to put them away.

Memory management refers to the process of handling computer memory during program execution. It involves allocating memory when needed, deallocating it when it’s no longer required (freeing), and optimizing memory usage to ensure efficient performance.

  • Allocation: Requesting and assigning portions of memory to programs or processes as needed. This is typically done dynamically during runtime.
  • Deallocation: Releasing memory that is no longer in use, allowing it to be reused for other purposes. Failure to deallocate memory can lead to memory leaks, where memory is consumed unnecessarily.
  • Optimization: Organizing and managing memory in a way that minimizes fragmentation and maximizes efficiency. This includes strategies such as memory pooling, garbage collection, and memory compaction.

STATIC MEMORY

Static allocation refers to the process where memory for variables is allocated at compile time. This means the amount of memory required is determined when the program is compiled, and the memory is reserved for the entire duration of the program’s execution.

Let’s take an example, we you create an array of 50 characters to store the names of participants in a workshop, C will reserve space for 50 characters, which is typically 50 bytes of memory (size of char datatype = 1):

char participants[50];
printf("%lu", sizeof(participants)); // 1(size of char) * 50 bytes

However, when the workshop begins, it becomes clear that the longest name only requires 30 characters. This results in 20 characters of unused, wasted space.

Since the size of the array cannot be changed, this results in unnecessary reserved memory.

Although the program will still function correctly, having many such instances can lead to suboptimal performance due to inefficient memory usage.

To better manage memory allocation, consider using dynamic memory allocation:

DYNAMIC MEMORY

Dynamic allocation refers to the process where memory is allocated at runtime, as needed. This allows programs to use memory more flexibly and efficiently. In C, dynamic memory allocation is managed using standard library functions like malloc, calloc, realloc, and free.

Unlike static memory, dynamic memory gives you complete control over how much memory is being used at any time. You can write code to determine the exact amount of memory needed and allocate it accordingly.

Dynamic memory isn’t tied to a specific variable; it can only be accessed via pointers.

To allocate dynamic memory, you can use the malloc() or calloc() functions, which require including the <stdlib.h> header. These functions allocate memory and return a pointer to the allocated memory’s address.

malloc-

Allocates a specified number of bytes and returns a pointer to the allocated memory. The memory is not initialized. malloc accepts one parameter:

Syntax:

void *malloc(size_t size);
  • size_t size: The number of bytes to allocate.

Example

#include <stdio.h>
#include <stdlib.h>

int main() {
int *ptr = (int *)malloc(5 * sizeof(int)); // Allocate memory for an array of 5 integers
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}

// Use the allocated memory
for (int i = 0; i < 5; i++) {
ptr[i] = i + 1;
printf("%d ", ptr[i]);
}
printf("\n");

free(ptr); // Free the allocated memory
return 0;
}

calloc-

Allocates memory for an array of elements, initializes them to zero, and returns a pointer to the allocated memory. calloc() accepts two parameters:

Syntax:

void *calloc(size_t num, size_t size);
  • size_t num : The number of elements to allocate.
  • size_t size : The size of each element in bytes.

Example:

#include <stdio.h>
#include <stdlib.h>

int main() {
int *ptr = (int *)calloc(5, sizeof(int)); // Allocate and initialize memory for an array of 5 integers
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}

// Use the allocated memory
for (int i = 0; i < 5; i++) {
printf("%d ", ptr[i]); // All elements are initialized to 0
}
printf("\n");

free(ptr); // Free the allocated memory
return 0;
}

realloc()-

Changes the size of previously allocated memory. The content is copied to the new memory if necessary, and the old memory is freed. realloc() accepts two parameters.
If the initially reserved memory is insufficient, reallocating allows you to increase its size while preserving the existing data. The realloc() function is used to adjust the size of allocated memory dynamically.

Syntax

void *realloc(void *ptr, size_t size);
  • void *ptr : A pointer to the memory block previously allocated with malloc, calloc, or realloc.
  • size_t size : The new size of the memory block in bytes.

Examples:

#include <stdio.h>
#include <stdlib.h>

int main() {
int *ptr = (int *)malloc(5 * sizeof(int)); // Allocate memory for an array of 5 integers
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}

// Use the allocated memory
for (int i = 0; i < 5; i++) {
ptr[i] = i + 1;
printf("%d ", ptr[i]);
}
printf("\n");

// Resize the memory block to hold 10 integers
ptr = (int *)realloc(ptr, 10 * sizeof(int));
if (ptr == NULL) {
printf("Memory reallocation failed\n");
return 1;
}

// Use the resized memory
for (int i = 0; i < 10; i++) {
printf("%d ", ptr[i]);
}
printf("\n");

free(ptr); // Free the allocated memory
return 0;
}

free()-

Deallocates the memory previously allocated by malloc(), calloc(), or realloc(). It does not change the value of the pointer. free() accepts one parameter.
Once memory is no longer needed, deallocation, also known as “freeing,” becomes essential. Dynamic memory remains reserved until deallocated or until the program terminates. Once deallocated, the memory becomes available for other programs or other parts of the same program.

Syntax

void free(void *ptr);
  • void *ptr : A pointer to the memory block to be deallocated.

Example:

#include <stdio.h>
#include <stdlib.h>

int main() {
int *ptr = (int *)malloc(5 * sizeof(int)); // Allocate memory for an array of 5 integers
if (ptr == NULL) {
printf("Memory allocation failed\n");
return 1;
}

// Use the allocated memory
for (int i = 0; i < 5; i++) {
ptr[i] = i + 1;
printf("%d ", ptr[i]);
}
printf("\n");

free(ptr); // Free the allocated memory
return 0;
}

The return types of malloc(), calloc(), and realloc() are void*, representing a generic pointer in C. This flexibility allows memory allocation for any data type. When using these functions, you cast the returned void* to the desired pointer type, such as int* for an array of integers.

MEMORY LEAK

A memory leak occurs in a computer program when it dynamically allocates memory but fails to release it properly when it is no longer needed. As a result, the program continues to hold onto memory that cannot be accessed or used, effectively “leaking” memory. Over time, if memory leaks accumulate, they can gradually consume all available memory resources, leading to degraded performance, system instability, or even program crashes. Identifying and fixing memory leaks is important for ensuring efficient and reliable operation of software applications.

Let’s say you’re developing a program in C that manages a list of tasks. Each task is represented by a structure containing information such as its ID, description, and priority level. To manage these tasks, you decide to use dynamic memory allocation to create a linked list structure.

Checkout these examples for better understanding

Here’s a simplified but advanced example of how you might implement this:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Structure to represent a task
typedef struct Task {
int id;
char description[50];
int priority;
struct Task *next;
} Task;

// Function to add a new task to the linked list
void addTask(Task **head, int id, const char *description, int priority) {
Task *newTask = (Task *)malloc(sizeof(Task));
if (newTask == NULL) {
printf("Memory allocation failed\n");
return;
}
newTask->id = id;
strcpy(newTask->description, description);
newTask->priority = priority;
newTask->next = *head;
*head = newTask;
}

// Function to print all tasks in the linked list
void printTasks(Task *head) {
printf("Tasks:\n");
while (head != NULL) {
printf("ID: %d, Description: %s, Priority: %d\n", head->id, head->description, head->priority);
head = head->next;
}
}

// Function to free memory allocated for the linked list
void freeTasks(Task *head) {
Task *temp;
while (head != NULL) {
temp = head;
head = head->next;
free(temp);
}
}

int main() {
Task *head = NULL;

// Add tasks to the linked list
addTask(&head, 1, "Complete report", 2);
addTask(&head, 2, "Attend meeting", 1);
addTask(&head, 3, "Reply to emails", 3);

// Print all tasks
printTasks(head);

// Free memory allocated for tasks
freeTasks(head);

return 0;
}

Understanding memory management & memory allocation in C is crucial for efficient and reliable programming. Whether using static allocation for fixed-size data or dynamic allocation for flexibility, managing memory properly ensures optimal performance and resource utilization. By mastering memory allocation techniques and being mindful of potential pitfalls like memory leaks, programmers can create robust and efficient software solutions.

Keep Learning :)

--

--

Dev Frank

Passionate tech enthusiast, diving deep into the world of software engineering. Thrilled to share insights with the world. A Software engineering student.