Reading values from the memory block which created/mofied malloc realloc?
Categories:
Understanding and Accessing Dynamically Allocated Memory in C

Explore how malloc
, calloc
, and realloc
manage memory, and learn the correct techniques for reading and modifying data within these dynamically allocated blocks.
Dynamic memory allocation is a fundamental concept in C programming, allowing programs to request memory at runtime rather than compile time. Functions like malloc
, calloc
, and realloc
are crucial for managing this memory. However, correctly accessing and manipulating data within these dynamically allocated blocks can be a source of common errors, such as segmentation faults or memory leaks. This article will demystify dynamic memory, explain how these functions work, and provide clear examples on how to safely read from and modify the memory they provide.
The Basics of Dynamic Memory Allocation
In C, memory is typically divided into several segments: text, data, BSS, stack, and heap. Dynamic memory allocation primarily deals with the heap. Unlike stack memory, which is automatically managed, heap memory must be explicitly requested and released by the programmer. Failure to release allocated memory leads to memory leaks, while attempting to access memory that has not been allocated or has been freed results in undefined behavior.
malloc
(memory allocation) allocates a specified number of bytes and returns a pointer to the beginning of the block. The allocated memory is uninitialized, meaning it contains garbage values.
calloc
(contiguous allocation) allocates a specified number of elements of a given size, initializing all bytes to zero. It's often preferred when you need zero-initialized memory.
realloc
(re-allocation) changes the size of an already allocated memory block. It can either expand or shrink the block. If the original block cannot be resized in place, realloc
allocates a new block, copies the contents of the old block to the new one, and frees the old block. It's crucial to handle the return value of realloc
carefully, as it might return NULL
if allocation fails.
flowchart TD A[Program Start] --> B{Need Dynamic Memory?} B -->|Yes| C{Choose Allocator} C --> D{malloc(size)} C --> E{calloc(num, size)} C --> F{realloc(ptr, new_size)} D --> G[Uninitialized Memory Block] E --> H[Zero-Initialized Memory Block] F --> I[Resized/New Memory Block] G --> J[Access/Modify Data] H --> J I --> J J --> K{Done with Memory?} K -->|Yes| L[free(ptr)] L --> M[Memory Released] K -->|No| J B -->|No| N[Program End] M --> N
Workflow of Dynamic Memory Allocation and Deallocation
Accessing and Modifying Allocated Memory
Once memory is allocated using malloc
, calloc
, or realloc
, the function returns a void*
pointer. This pointer must be cast to the appropriate data type before it can be dereferenced and used to access the allocated memory. The type of the pointer determines how many bytes are accessed when you dereference it or perform pointer arithmetic.
For example, if you allocate memory for an array of integers, you should cast the void*
to an int*
. Then, you can use array indexing (ptr[index]
) or pointer arithmetic (*(ptr + index)
) to read from or write to individual elements within the allocated block. It's vital to stay within the bounds of the allocated memory; accessing memory outside these bounds leads to undefined behavior, which can manifest as crashes or corrupted data.
When modifying memory, simply assign new values to the dereferenced pointer or array elements. Remember that realloc
might move the memory block, so always update your pointer with the new address returned by realloc
.
#include <stdio.h>
#include <stdlib.h>
int main() {
// --- Using malloc ---
int *arr_malloc;
int n_malloc = 5;
// Allocate memory for 5 integers
arr_malloc = (int *) malloc(n_malloc * sizeof(int));
if (arr_malloc == NULL) {
perror("malloc failed");
return 1;
}
printf("\n--- malloc example ---\n");
// Modify and read values
for (int i = 0; i < n_malloc; i++) {
arr_malloc[i] = i * 10; // Modify
printf("arr_malloc[%d] = %d\n", i, arr_malloc[i]); // Read
}
free(arr_malloc);
// --- Using calloc ---
float *arr_calloc;
int n_calloc = 3;
// Allocate memory for 3 floats, initialized to zero
arr_calloc = (float *) calloc(n_calloc, sizeof(float));
if (arr_calloc == NULL) {
perror("calloc failed");
return 1;
}
printf("\n--- calloc example (initial values) ---\n");
for (int i = 0; i < n_calloc; i++) {
printf("arr_calloc[%d] = %.2f\n", i, arr_calloc[i]); // Read (should be 0.00)
}
printf("\n--- calloc example (modified values) ---\n");
// Modify and read values
for (int i = 0; i < n_calloc; i++) {
arr_calloc[i] = (float)(i + 1) * 2.5; // Modify
printf("arr_calloc[%d] = %.2f\n", i, arr_calloc[i]); // Read
}
free(arr_calloc);
// --- Using realloc ---
char *str_realloc;
int initial_len = 5;
// Allocate initial memory for a string
str_realloc = (char *) malloc(initial_len * sizeof(char)); // For 4 chars + null terminator
if (str_realloc == NULL) {
perror("malloc failed for realloc example");
return 1;
}
snprintf(str_realloc, initial_len, "Hello"); // Copy "Hell" + null
printf("\n--- realloc example ---\n");
printf("Initial string: %s\n", str_realloc); // Read
int new_len = 15;
// Reallocate to a larger size
char *temp_str = (char *) realloc(str_realloc, new_len * sizeof(char));
if (temp_str == NULL) {
perror("realloc failed");
free(str_realloc); // Free original block if realloc fails
return 1;
}
str_realloc = temp_str; // Update pointer to new block
// Modify and read the reallocated memory
snprintf(str_realloc, new_len, "Hello, World!"); // Modify
printf("Reallocated string: %s\n", str_realloc); // Read
free(str_realloc);
return 0;
}
Example demonstrating malloc
, calloc
, and realloc
for memory allocation, modification, and reading.
malloc
, calloc
, and realloc
. If they return NULL
, it indicates that memory allocation failed, and attempting to dereference a NULL
pointer will lead to a segmentation fault. Handle these errors gracefully to prevent program crashes.Common Pitfalls and Best Practices
Dynamic memory management, while powerful, is prone to errors. Understanding and avoiding common pitfalls is key to writing robust C programs.
Memory Leaks: Forgetting to free
allocated memory is a classic memory leak. Over time, this can consume all available memory, leading to system instability or program termination. Always pair every malloc
, calloc
, or successful realloc
with a corresponding free
.
Dangling Pointers: After free(ptr)
is called, ptr
still holds the address of the freed memory. If you try to dereference ptr
after it's freed, it's undefined behavior. It's good practice to set ptr = NULL;
immediately after freeing it to prevent accidental use.
Double Free: Calling free
on the same memory block twice is also undefined behavior and can corrupt the heap. Setting pointers to NULL
after freeing helps prevent this.
Buffer Overflows/Underflows: Accessing memory outside the bounds of an allocated block (e.g., arr[n]
when arr
was allocated for n
elements, meaning valid indices are 0
to n-1
) can overwrite adjacent data or trigger segmentation faults. Always ensure your access patterns respect the allocated size.
Incorrect realloc
Usage: When realloc
fails, it returns NULL
but does not free the original memory block. If you assign the NULL
return directly back to your original pointer, you lose the reference to the original block, creating a memory leak. Always use a temporary pointer for the realloc
return value, and only assign it back if realloc
succeeds.