6.3 - Working with dynamic memory

6.3 - Working with dynamic memory

Dynamic memory management is crucial for handling scenarios where the size of the data structure or the number of elements is not known at compile time.

What is Dynamic Memory?

Dynamic memory refers to memory that is allocated at runtime, as opposed to static memory, which is allocated at compile time. Dynamic memory is often needed when:

  • The size of an array or data structure is not known beforehand.
  • You need to create or free memory on demand during the execution of the program.

Dynamic memory in C is managed using the heap, a region of memory that can grow or shrink as needed during program execution.

Dynamic Memory Functions

The C Standard Library provides four main functions for working with dynamic memory:

  1. malloc()
  2. calloc()
  3. realloc()
  4. free()

All these functions are declared in the header <stdlib.h>.

stdlib.h is provided by operating system and the implementation may vary depending on the OS

malloc()

malloc allocates a specified amount of memory, returning a pointer to the beginning of the allocated block. The memory is uninitialized, meaning it contains indeterminate values.

Syntax:

void* malloc(size_t size);
  • size: The amount of memory to allocate in bytes.
  • Return Value: A pointer to the allocated memory. If the allocation fails, malloc() returns NULL.

Example:

int *arr = (int*) malloc(5 * sizeof(int));
if (arr == NULL) {
    // Handle memory allocation failure
}

calloc()

calloc is similar to malloc, but it initializes the allocated memory to zero. It allocates memory for an array of elements and initializes all bytes to 0.

Syntax:

void* calloc(size_t num, size_t size);
  • num: The number of elements.
  • size: The size of each element in bytes.
  • Return Value: A pointer to the allocated memory, or NULL if allocation fails.

Example:

int *arr = (int*) calloc(5, sizeof(int));
if (arr == NULL) {
    // Handle memory allocation failure
}

Here, calloc allocates memory for 5 integers and initializes them all to zero.

realloc()

realloc is used to resize a previously allocated memory block. It can either increase or decrease the size of the allocated block. If you increase the size, the new area will contain uninitialized memory (in case of malloc). If you decrease it, the extra memory is released.

Syntax:

void* realloc(void *ptr, size_t size);
  • ptr: A pointer to the previously allocated memory. If ptr is NULL, realloc behaves like malloc().
  • size: The new size in bytes.

Example:

arr = (int*) realloc(arr, 10 * sizeof(int));
if (arr == NULL) {
    // Handle memory allocation failure
}

This resizes the memory block pointed to by arr to hold 10 integers.

free()

free deallocates the memory that was previously allocated with malloc, calloc, or realloc. Once memory is freed, it can no longer be accessed, and attempting to do so results in undefined behavior.

Syntax:

void free(void *ptr);
  • ptr: A pointer to the memory block to be freed. If ptr is NULL, no action is performed.

Example:

free(arr);

Memory Management Best Practices

Dynamic memory management is a powerful feature, but it also comes with risks like memory leaks and undefined behavior. Here are some best practices to follow:

Check for Memory Allocation Failure

Memory allocation functions (malloc, calloc, realloc) return NULL if they fail to allocate memory. Always check the returned pointer to ensure that memory was successfully allocated.

Example:

int *arr = (int*) malloc(100 * sizeof(int));
if (arr == NULL) {
    printf("Memory allocation failed!\n");
    exit(1); // Exit the program if memory allocation fails
}

Avoid Memory Leaks

A memory leak occurs when a program allocates memory but does not free it. This results in a gradual increase in memory usage over time. To avoid memory leaks:

  • Free all dynamically allocated memory before the program terminates.
  • Ensure every call to malloc, calloc, or realloc has a corresponding free() call.

Example:

int *arr = (int*) malloc(10 * sizeof(int));
// Use the allocated memory
free(arr); // Make sure to free the memory

Avoid Dangling Pointers

A dangling pointer is a pointer that points to memory that has been freed. Accessing memory through a dangling pointer leads to undefined behavior. To avoid this, set the pointer to NULL after freeing it.

Example:

free(arr);
arr = NULL;

Use realloc() Carefully

When using realloc(), if it fails, it returns NULL but does not free the original memory block. You should always handle the case where realloc() fails and ensure that the original block is not lost.

Example:

int *temp = realloc(arr, 20 * sizeof(int));
if (temp != NULL) {
    arr = temp; // Update arr only if realloc succeeded
} else {
    // Handle memory allocation failure
}

Zeroing Freed Memory

Although free() deallocates memory, it doesn’t overwrite or clear the contents of the memory block. It’s sometimes advisable to overwrite sensitive information before freeing memory (e.g., passwords or cryptographic keys) to avoid accidental leaks.

Example:

memset(arr, 0, sizeof(arr));
free(arr);

Common Pitfalls and Undefined Behavior

C provides no automatic garbage collection or bounds checking, so it’s essential to be cautious while working with dynamic memory. Some common pitfalls include:

  • Double Free: Calling free() more than once on the same pointer leads to undefined behavior. Example of incorrect use:
    free(arr);
    free(arr); // Undefined behavior
    
  • Accessing Freed Memory: Once memory is freed, accessing it (also called a “use-after-free” error) results in undefined behavior. Example of incorrect use:
    free(arr);
    printf("%d\n", arr[0]); // Undefined behavior
    
  • Memory Leaks: Forgetting to free dynamically allocated memory can lead to memory leaks, especially in long-running programs.

Improved Safety

  • Static analysis tools: Tools like Valgrind, AddressSanitizer, and static analyzers can help detect memory leaks, use-after-free bugs, and other memory management issues.

Memory Alignment Considerations

In some systems, especially on architectures with strict alignment requirements, it may be important to ensure that dynamically allocated memory is properly aligned. The aligned_alloc() function, introduced in C11, allows for allocating memory with specific alignment.

Syntax:

void* aligned_alloc(size_t alignment, size_t size);
  • alignment: The alignment requirement (must be a power of 2).
  • size: The size of the memory block to allocate.

Example:

int *arr = (int*) aligned_alloc(16, 100 * sizeof(int));

Summary

Dynamic memory management relies heavily on using malloc(), calloc(), realloc(), and free() properly.

By following best practices, including checking for allocation failures, avoiding memory leaks, and preventing dangling pointers, you can efficiently manage memory in C programs.



© 2025 Easy and fast Programming guide For C

Powered by Tessera for Jekyll