Question
In C and C++, what does array-to-pointer conversion, often called array decay, mean? How does it work, and how is it related to pointers to arrays?
Short Answer
By the end of this page, you will understand what array-to-pointer conversion means in C and C++, why arrays are often treated like pointers in expressions, and why an array is still not the same thing as a pointer. You will also learn the difference between a decayed array expression and a pointer to an array, which is a separate type.
Concept
In C and C++, an array and a pointer are closely related, but they are not the same thing.
When an array is used in many expressions, the language automatically converts the array expression into a pointer to its first element. This automatic conversion is called array-to-pointer conversion or array decay.
For example:
int numbers[4] = {10, 20, 30, 40};
int* p = numbers;
Here, numbers is an array of 4 int values. But in the assignment to p, numbers is converted to &numbers[0], which is a pointer to the first element.
So this line behaves like:
int* p = &numbers[0];
Why this matters
This concept matters because it explains several common behaviors:
- Why arrays can be passed to functions that take pointers
- Why
arr[i]works with pointers too - Why
sizeof(arr)is different inside and outside some functions - Why arrays and pointers feel similar but behave differently
Important idea
An array is a real object that stores multiple elements. A pointer is a variable that stores an address.
Array decay does not mean the array becomes a pointer permanently. It means that in certain expressions, the array is converted to a pointer value.
Common conversion result
If you have:
int arr[5];
then in most expressions, arr becomes:
int* // pointer to first element
Cases where decay does not happen
There are important exceptions where the array stays an array:
sizeof(arr)&arrdecltype(arr)in C++typeid(arr)in some C++ contexts- direct initialization of a reference to an array in C++
Example:
int arr[5];
sizeof(arr); // size of the whole array, not pointer size
&arr; // pointer to the whole array, type: int (*)[5]
Relation to pointer to array
This is where many learners get confused.
These two types are different:
int* p; // pointer to int
int (*q)[5]; // pointer to an array of 5 ints
If arr is int arr[5];, then:
arrusually decays toint*&arrisint (*)[5]
So:
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr; // okay
int (*q)[5] = &arr; // okay
They may print similar addresses, but their types are different, and pointer arithmetic behaves differently.
p + 1; // moves by one int
q + 1; // moves by one whole array of 5 ints
That type difference is the key idea.
Mental Model
Think of an array as a row of lockers and a pointer as a piece of paper containing an address.
- The array is the actual row of lockers.
- The pointer is just a stored location.
When array decay happens, the language says:
“If you use the array in this expression, I will give you the address of the first locker.”
That does not mean the row of lockers has turned into paper. The array still exists as a full array. You are just being handed the starting address.
Now think about a pointer to an array as a paper that says:
“Here is the address of the entire row as one unit.”
That is different from:
“Here is the address of the first locker.”
These addresses may look similar, but the meaning of moving forward from them is different:
- from the first locker: move by one element
- from the whole row: move by one whole row
Syntax and Examples
Basic syntax
int arr[3] = {1, 2, 3};
int* p = arr; // array decays to pointer to first element
int* p2 = &arr[0]; // equivalent result
Array and pointer access
#include <iostream>
using namespace std;
int main() {
int arr[3] = {10, 20, 30};
int* p = arr;
cout << arr[0] << "\n"; // 10
cout << p[0] << "\n"; // 10
cout << *(arr + 1) << "\n"; // 20
cout << *(p + 2) << "\n"; // 30
}
Because arr often decays to int*, pointer-style access works.
Pointer to array syntax
int arr[3] = {10, 20, 30};
int (*wholeArrayPtr)[3] = &arr;
This declaration means:
wholeArrayPtris a pointer- it points to an array of 3 integers
Access example:
#include <iostream>
using namespace std;
int main() {
int arr[3] = {10, 20, 30};
int (*wholeArrayPtr)[3] = &arr;
cout << (*wholeArrayPtr)[0] << "\n"; // 10
cout << (*wholeArrayPtr)[1] << "\n"; // 20
cout << (*wholeArrayPtr)[2] << "\n"; // 30
}
Function parameter example
#include <iostream>
using namespace std;
void printFirst(int* p) {
cout << p[0] << "\n";
}
int main() {
int arr[3] = {7, 8, 9};
printFirst(arr); // arr decays to int*
}
This is one reason decay is so important: arrays are commonly passed to functions as pointers.
Step by Step Execution
Consider this example:
#include <iostream>
using namespace std;
int main() {
int arr[3] = {5, 10, 15};
int* p = arr;
int (*q)[3] = &arr;
cout << arr[1] << "\n";
cout << p[1] << "\n";
cout << (*q)[1] << "\n";
}
Step by step
1. Create the array
int arr[3] = {5, 10, 15};
Memory contains three int values in sequence.
arr[0]is5arr[1]is10arr[2]is15
2. Assign to p
int* p = arr;
Here arr decays to a pointer to the first element.
So p stores the address of arr[0].
3. Assign to q
int (*q)[3] = &arr;
Here &arr means “address of the whole array.”
So q points to the entire array object, not just the first element.
4. Read arr[1]
cout << arr[1] << "\n";
This prints 10.
5. Read p[1]
cout << p[1] << "\n";
Since p points to the first element, p[1] means one element forward, so it also prints 10.
6. Read (*q)[1]
cout << (*q)[1] << "\n";
qpoints to the whole array*qgives the actual array(*q)[1]accesses the second element
This also prints 10.
Key lesson
All three expressions can access the same value, but they get there using different types:
arris an arraypis a pointer tointqis a pointer to an array of 3intvalues
Real World Use Cases
Passing arrays to functions
In C and C++ code, arrays are often passed into functions for processing:
void process(int* data, int size);
A real array can be passed because it decays to int*.
Working with buffers
Many programs use character buffers or numeric buffers:
char buffer[256];
Functions often receive a pointer to the first element and then work through the buffer.
Interoperability with low-level APIs
System libraries, C libraries, and embedded code often expect pointers rather than high-level container types. Array decay makes fixed arrays usable with those APIs.
Iterating through contiguous memory
Code that processes image data, sensor data, network packets, or audio samples often treats arrays as a starting address plus length.
Multidimensional arrays
Pointers to arrays become especially important with 2D arrays:
int grid[2][3];
When passed correctly, developers often use pointer-to-array types to preserve row size information.
Real Codebase Usage
In real projects, developers usually rely on this concept in a few common ways.
Function parameters
Traditional C-style functions often use:
void printValues(const int* values, int count);
The caller may pass an array, and it decays automatically.
Guard clauses with pointer inputs
When functions receive pointers, code often validates them early:
void printValues(const int* values, int count) {
if (values == nullptr || count <= 0) {
return;
}
for (int i = 0; i < count; i++) {
std::cout << values[i] << "\n";
}
}
Preserving array size in C++
Because decay loses size information, modern C++ code often prefers references to arrays or containers like std::array and std::vector.
template <size_t N>
void printArray(const int (&arr)[N]) {
for (size_t i = 0; i < N; i++) {
std::cout << arr[i] << "\n";
}
}
Here the array does not decay, so the size N is preserved.
Using pointer-to-array for matrix-like data
For fixed-width rows, pointer-to-array types help keep the structure accurate:
void printRows(int (*rows)[3], int rowCount) {
for (int i = 0; i < rowCount; i++) {
for (int j = 0; j < 3; j++) {
std::cout << rows[i][j] << ' ';
}
std::cout << "\n";
}
}
This is safer than pretending a 2D array is just int* because row width matters.
Common Mistakes
Mistake 1: Thinking arrays and pointers are identical
They are related, but not identical.
int arr[3] = {1, 2, 3};
int* p = arr;
arris an array objectpis a pointer variable
You cannot assign a new address to arr.
arr = p; // error
Mistake 2: Losing array size in function parameters
void showSize(int arr[]) {
std::cout << sizeof(arr) << "\n";
}
This is misleading. In a function parameter, arr[] is treated like int* arr, so sizeof(arr) gives the size of a pointer, not the whole array.
Mistake 3: Confusing arr with &arr
int arr[5];
arrusually decays toint*&arrisint (*)[5]
Broken assumption:
int* p = &arr; // error
Correct versions:
int* p = arr;
int (*q)[5] = &arr;
Mistake 4: Misunderstanding pointer arithmetic
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;
int (*q)[5] = &arr;
p + 1moves by oneintq + 1moves by one whole array of 5intvalues
If you treat these as interchangeable, you will get wrong results.
Mistake 5: Expecting decay everywhere
Decay does not happen in all contexts.
int arr[5];
std::cout << sizeof(arr) << "\n";
This gives the size of the whole array.
Mistake 6: Forgetting length information
After decay, only the pointer remains. The number of elements is not automatically included.
void print(int* p) {
// no built-in way to know how many elements p points to
}
Always pass the size separately, or use safer C++ alternatives like std::array, std::vector, or std::span when available.
Comparisons
| Concept | Type example | What it represents | Pointer arithmetic |
|---|---|---|---|
| Array | int arr[5] | 5 actual int values stored together | Not a pointer variable |
| Decayed array expression | arr in many expressions | Pointer to first element | Moves by one element |
| Pointer to element | int* p | Address of an int | Moves by one int |
| Pointer to whole array | int (*q)[5] | Address of an array of 5 int values | Moves by one whole array |
arr vs &arr
| Expression | Resulting meaning | Type |
|---|---|---|
arr | usually first element address after decay | int* |
&arr | address of the whole array | int (*)[5] |
Function parameter forms
| Parameter syntax | Actual meaning in function | Notes |
|---|---|---|
void f(int arr[]) | void f(int* arr) | array size is not preserved |
void f(int* arr) | pointer parameter | same as above |
void f(int (&arr)[5]) | reference to array of 5 ints | size preserved in C++ |
Key comparison
Array decay gives you a pointer to the first element. A pointer to an array gives you a pointer to the entire array object. Those are related, but not the same.
Cheat Sheet
int arr[5]; // array of 5 ints
int* p = arr; // arr decays to int*
int* p2 = &arr[0]; // same address as first element
int (*q)[5] = &arr; // pointer to whole array
Rules to remember
- Arrays are not pointers.
- In many expressions, an array decays to a pointer to its first element.
arrusually becomes&arr[0].&arrmeans address of the whole array.arrand&arrcan print similar addresses, but their types differ.- In function parameters,
int arr[]is treated asint* arr. - After decay, array length information is lost.
No-decay contexts
Decay does not happen with:
sizeof(arr)&arr- array references in C++
decltype(arr)in C++
Quick type guide
int arr[5]; // array
int* p = arr; // pointer to int
int (*q)[5]; // pointer to array of 5 ints
Pointer arithmetic
p + 1 // next int
q + 1 // next array of 5 ints
FAQ
What does array decay mean in C or C++?
It means an array expression is automatically converted to a pointer to its first element in many contexts.
Is an array the same as a pointer?
No. An array is a real object containing elements. A pointer is a variable that stores an address.
What is the difference between arr and &arr?
arr usually decays to a pointer to the first element. &arr is a pointer to the whole array.
Why does sizeof(arr) give a different result from sizeof in a function parameter?
Outside the function, arr is still an array, so sizeof(arr) gives the full array size. In a parameter like int arr[], it is treated as a pointer, so sizeof(arr) gives pointer size.
What is a pointer to an array?
It is a pointer whose target is the entire array object, such as int (*p)[5].
Why do arr and &arr sometimes show the same address?
They can refer to the same starting memory location, but they have different types and different pointer arithmetic behavior.
Does array decay happen every time I use an array?
No. It does not happen in contexts like sizeof(arr) and &arr, and it can be prevented by binding a reference to the array in C++.
Mini Project
Description
Build a small C++ program that demonstrates the difference between an array, a decayed array expression, and a pointer to the whole array. This project is useful because many bugs come from treating these types as if they were identical.
Goal
Write a program that prints values, sizes, and addresses to show how array decay works and how it differs from a pointer to an array.
Requirements
- Create an integer array with at least 4 elements.
- Store the decayed array in an
int*pointer. - Store the whole array address in a pointer-to-array variable.
- Print the first and second elements using all three forms.
- Show the difference between
sizeof(arr)andsizeof(pointer). - Demonstrate how
p + 1andq + 1represent different pointer movements.
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.
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.
Bus Error vs Segmentation Fault in C: SIGBUS and SIGSEGV Explained
Learn what bus errors and segmentation faults mean in C, why they happen, how SIGBUS differs from SIGSEGV, and how to debug both safely.