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:
free(ptr);
free() can look up the allocator's internal metadata associated with that pointer and determine how to release the block.
Why this matters
This design lets the allocator:
- manage heap memory efficiently
- merge free blocks
- track used and unused memory
- support
realloc()
Without metadata, the allocator would not be able to safely manage dynamic memory.
Important distinction: pointer vs allocated block
A pointer variable does not know the size of the object it points to.
But the allocator may know the size of the heap block because it stored that information when the block was allocated.
That is why:
free(ptr)works without a size argumentsome_function(ptr)usually still needs a separate length if it must process an array
Can you do this in your own code?
Yes, but only if you control the allocation strategy.
You could build a custom abstraction that stores length metadata near the data or inside a struct. But ordinary pointers in C do not automatically carry length information, and functions receiving a raw pointer still cannot magically discover array length in a general, portable way.
So the short answer is:
free()does not know from the pointer alone- it knows because the memory allocator stored extra hidden information
- your own functions can imitate this only if you design your data structures or allocator to do so
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++) {
printf("%d\n", numbers[i]);
}
free(numbers);
numbers = NULL;
return 0;
}
What this example shows
malloc()allocates space for 5 integers.numberspoints to the first integer.- The program uses
countto know how many elements to process. free(numbers)works withoutcountbecause the allocator already recorded the block information.
Example: a function still needs the length
#include <stdio.h>
void print_array(const int *arr, size_t length) {
for (size_t i = 0; i < length; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
This function needs length because arr is just a pointer. It has no built-in way to know how many integers are valid.
A common custom solution: store length in a struct
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *data;
size_t length;
} IntArray;
void print_int_array(const IntArray *arr) {
for (size_t i = 0; i < arr->length; i++) {
printf("%d ", arr->data[i]);
}
printf("\n");
}
This does not make pointers smarter. It simply packages the pointer and its length together in one object.
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.
It does not see the metadata.
4. free(p) is called
The allocator recognizes that p points to a block it previously returned.
Using its internal layout, it finds the corresponding metadata and marks that block as free.
5. The memory becomes invalid to use through p
After free(p):
pstill contains the old address value- but that memory is no longer yours to use
- reading or writing through
pis undefined behavior
A safer pattern is:
free(p);
p = NULL;
Why ordinary functions cannot do this
If you write:
void process(int *p) {
/* no length information here */
}
this function does not have allocator privileges or a portable standard API to discover the number of array elements from p. Even if some platform-specific trick exists, it is not general C array-length information.
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
It frees the block as a heap allocation, not as a typed container.
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.
Guard clauses and validation
Real code often validates inputs before using them:
void print_array(const int *arr, size_t length) {
if (arr == NULL || length == 0) {
return;
}
for (size_t i = 0; i < length; i++) {
/* use arr[i] */
}
}
Error handling with cleanup
A typical allocation pattern is:
char *buffer = malloc(1024);
if (buffer == NULL) {
return -1;
}
/* use buffer */
free(buffer);
buffer = NULL;
Custom allocators
Some codebases implement their own allocators for:
- game engines
- embedded systems
- high-performance servers
- memory pools
Those allocators also store metadata, but that is part of the allocator design, not something built into all pointers in C.
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:
int x = 10;
free(&x);
Why this is wrong:
free()must only be used on pointers returned by allocation functions likemalloc(),calloc(), orrealloc()- otherwise behavior is undefined
Mistake 3: double free
Broken example:
int *p = malloc(sizeof(int));
free(p);
free(p);
Why this is wrong:
- freeing the same block twice is undefined behavior
Safer approach:
free(p);
p = NULL;
Mistake 4: using memory after free
Broken example:
int *p = malloc(sizeof(int));
free(p);
*p = 42;
Why this is wrong:
- once freed, the memory no longer belongs to you
Mistake 5: confusing allocator block size with logical element count
Even if the allocator knows the allocated block size, your function still may not know:
- how many elements are initialized
- what type is stored
- whether the memory is being used as an array at all
So block size is not a replacement for proper API design.
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 |
free() vs normal array-processing functions
| Feature | free() | print_array(arr, len) |
|---|---|---|
| Needs length argument | No | Yes |
| Uses hidden allocator metadata | Yes | No |
| Knows logical number of elements | Not necessarily | Yes, if caller passes it |
| Works on any pointer | No, only valid allocated pointers | Depends on function design |
Hidden metadata vs explicit length
| Approach | Advantage | Disadvantage |
|---|---|---|
| Hidden allocator metadata | Convenient for memory management | Not general-purpose array length info |
| Explicit length parameter | Clear and portable | Extra argument to pass |
| Struct containing length | Groups related data cleanly | Requires custom type |
| Sentinel value | No extra length variable | Only works for some data formats, like null-terminated strings |
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.- After
free(ptr), do not useptr. - Setting a freed pointer to
NULLcan help avoid accidental reuse. - Allocator block size is not the same as logical data length.
Safe memory pattern
int *p = malloc(n * sizeof(int));
if (p == NULL) {
/* handle error */
}
/* use p */
free(p);
p = NULL;
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
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
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.