Question
In C, free() accepts a pointer without requiring a separate size argument. How does free() know how much dynamically allocated memory to release?
In many other cases, when passing an array to a function, you must also pass its length because the function cannot determine the array size from the pointer alone. For example, an array with 10 elements usually needs 10 passed alongside it.
Why does free() not need that extra size information? Also, is it possible to use the same technique in your own functions so you do not need to keep passing array lengths around?
int *arr = malloc(10 * sizeof(int));
if (arr != NULL) {
free(arr);
}
Short Answer
By the end of this page, you will understand why free() can release memory without being told the size, how memory allocators store bookkeeping information, and why ordinary functions still cannot infer array length from a pointer. You will also learn when similar techniques are possible in your own C programs and what trade-offs they involve.
Concept
In C, a pointer is just an address. By itself, it does not include the size of the memory block it points to.
That is why this function cannot know how many elements are in arr:
void print_array(int *arr) {
/* arr is only a pointer here */
}
However, free() is different because it works with memory that was previously allocated by functions like malloc(), calloc(), or realloc().
The key idea: the allocator keeps metadata
When you call malloc(), the memory allocator usually reserves:
- the memory you asked for, and
- some extra hidden bookkeeping data, often stored just before the returned block
That hidden data may include:
- the size of the block
- allocation status
- links to other blocks used internally by the allocator
Conceptually, it often looks like this:
[ allocator metadata ][ bytes returned to your program ]
^
pointer from malloc()
So when you later call:
Mental Model
Think of malloc() like a hotel front desk giving you a room key.
- You receive only the room key: that is your pointer.
- The hotel keeps the reservation details in its system: that is the allocator metadata.
- When you check out, you hand back the key.
- You do not need to say how big the room is, because the hotel already knows.
A normal function that receives only a pointer is like someone who finds the room key but has no access to the hotel database. The key gives an address, but not the reservation details.
So:
- your pointer = the key
- allocator metadata = the reservation record
free()= the front desk checking you out using its own records
Syntax and Examples
The basic syntax for dynamic allocation and release in C is:
#include <stdlib.h>
int *arr = malloc(10 * sizeof(int));
if (arr == NULL) {
/* allocation failed */
}
free(arr);
arr = NULL;
Example: allocation and release
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t count = 5;
int *numbers = malloc(count * sizeof(int));
if (numbers == NULL) {
return 1;
}
for (size_t i = 0; i < count; i++) {
numbers[i] = (int)(i * 10);
}
for (size_t i = 0; i < count; i++) {
(, numbers[i]);
}
(numbers);
numbers = ;
;
}
Step by Step Execution
Consider this example:
#include <stdlib.h>
int main(void) {
int *p = malloc(4 * sizeof(int));
if (p == NULL) {
return 1;
}
free(p);
return 0;
}
Step by step
1. malloc(4 * sizeof(int))
The allocator receives a request for enough bytes to hold 4 integers.
If sizeof(int) is 4 on that system, the request is 16 bytes.
2. The allocator reserves more than 16 bytes internally
The allocator may reserve:
- some hidden metadata
- the 16 usable bytes for your program
Conceptually:
[ metadata ][ 16 bytes for p[0]..p[3] ]
^
p points here
3. p receives the address of the usable region
Your code sees only the pointer to the data area.
Real World Use Cases
This concept appears often in real C programs.
Dynamic buffers
Programs allocate buffers for:
- file contents
- network packets
- user input
- image or audio data
The allocator tracks block size internally so the memory can later be freed.
Resizable data structures
Structures like dynamic arrays, vectors, and string builders often use:
malloc()for initial storagerealloc()to growfree()to release memory
These structures usually store their own logical length separately from the allocator's block size.
Libraries and APIs
Many C libraries return allocated objects that the caller must free later. For example:
- parsed configuration data
- decoded file contents
- generated strings
The caller often gets only a pointer, but free() still works because allocation metadata is managed by the memory allocator.
Binary data processing
If you allocate memory for raw bytes, free() can release it without knowing what the bytes represent. It does not care whether the block contains:
intvalues- characters
- structs
- serialized data
Real Codebase Usage
In real projects, developers usually separate memory ownership from logical data size.
Common pattern: pointer plus length
A common API style is:
void send_data(const unsigned char *data, size_t length);
Why?
- the function needs to know how many bytes are meaningful
- a pointer alone is not enough
- the memory might not even come from
malloc()
Common pattern: wrap data in a struct
typedef struct {
char *data;
size_t length;
size_t capacity;
} Buffer;
This is very common for:
- strings
- vectors
- file buffers
- protocol messages
Here:
length= how much data is currently usedcapacity= how much memory is allocated
The allocator may also know the raw block size internally, but your program still tracks the logical meaning.
Common Mistakes
Mistake 1: assuming a pointer knows array length
Broken example:
void print_array(int *arr) {
size_t length = sizeof(arr) / sizeof(arr[0]);
for (size_t i = 0; i < length; i++) {
printf("%d\n", arr[i]);
}
}
Why this is wrong:
- inside the function,
arris a pointer sizeof(arr)gives the size of the pointer, not the array
How to fix it:
void print_array(int *arr, size_t length) {
for (size_t i = 0; i < length; i++) {
printf("%d\n", arr[i]);
}
}
Mistake 2: freeing memory not returned by malloc()
Broken example:
x = ;
(&x);
Comparisons
| Concept | What it knows | Typical use | Notes |
|---|---|---|---|
| Raw pointer | Only an address | Accessing memory | Does not store length |
free(ptr) | Uses allocator metadata | Releasing heap memory | Works only for valid allocated blocks |
Function with ptr + length | Address and logical size | Processing arrays/buffers | Most common C API pattern |
| Struct with pointer and length | Address plus stored metadata | Safer data handling | Good for reusable abstractions |
| Custom allocator/container | Internal block bookkeeping | Specialized memory management | More control, more complexity |
vs normal array-processing functions
Cheat Sheet
Quick reference
- A C pointer stores an address, not array length.
free(ptr)works because the allocator stores hidden metadata for allocated blocks.- Only pass to
free()pointers returned by:malloc()calloc()realloc()
- Do not pass to
free():- stack addresses
- string literals
- already-freed pointers
- random interior pointers
Common pattern for arrays
void process(const int *arr, size_t length);
Common wrapper type
typedef struct {
int *data;
size_t length;
} IntArray;
Important rules
sizeof(pointer)is not array length.
FAQ
Why doesn't free() need the size of the memory block?
Because the memory allocator stores bookkeeping information about each allocated block, typically including its size.
Does a pointer in C know how many elements it points to?
No. A pointer is just an address. It does not store array length.
Can I get the array length from a pointer using sizeof?
Only if you still have the actual array in the same scope. Once it is passed to a function as a pointer, sizeof gives the pointer size instead.
Can I use allocator metadata in my own functions?
Not portably for general array processing. You can design your own container or custom allocator that stores length information, but raw pointers do not provide it automatically.
Is the size known to free() the same as the number of array elements I am using?
Not necessarily. The allocator may know the block size in bytes, but your program still needs to track logical length or element count.
Can I call free() on part of an allocated array?
No. You must pass the same pointer value originally returned by malloc(), calloc(), or realloc().
Why do strings often not need a separate length parameter?
C strings use a sentinel value: the null terminator \0. Functions can scan until they find it, although this is different from allocator metadata.
Mini Project
Description
Build a small dynamic integer array wrapper in C that stores both a pointer and its length. This demonstrates the correct way to avoid passing separate raw pointer and length values everywhere: package them together in a struct rather than expecting the pointer itself to know the size.
Goal
Create, print, and free a dynamic integer array using a struct that keeps the length alongside the allocated memory.
Requirements
- Define a struct containing an
int *pointer and asize_t lengthfield. - Write a function to allocate memory for a given number of integers.
- Write a function to print all elements using the stored length.
- Write a function to free the memory safely.
- Demonstrate the full flow in
main().
Keep learning
Related questions
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
C Pointer to Array vs Array of Pointers: How to Read Complex Declarations
Learn the difference between pointer-to-array and array-of-pointers in C, plus a simple rule for reading complex declarations correctly.