Question

Array-to-Pointer Conversion in C and C++ Explained

ccpparrayspointers

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)
  • &arr
  • decltype(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:

  • arr usually decays to int*
  • &arr is int (*)[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
Take Quiz

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:

  • wholeArrayPtr is 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] is 5
  • arr[1] is 10
  • arr[2] is 15

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";
  • q points to the whole array
  • *q gives 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:

  • arr is an array
  • p is a pointer to int
  • q is a pointer to an array of 3 int values

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.

Take Quiz

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;
  • arr is an array object
  • p is 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];
  • arr usually decays to int*
  • &arr is int (*)[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 + 1 moves by one int
  • q + 1 moves by one whole array of 5 int values

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

ConceptType exampleWhat it representsPointer arithmetic
Arrayint arr[5]5 actual int values stored togetherNot a pointer variable
Decayed array expressionarr in many expressionsPointer to first elementMoves by one element
Pointer to elementint* pAddress of an intMoves by one int
Pointer to whole arrayint (*q)[5]Address of an array of 5 int valuesMoves by one whole array

arr vs &arr

ExpressionResulting meaningType
arrusually first element address after decayint*
&arraddress of the whole arrayint (*)[5]

Function parameter forms

Parameter syntaxActual meaning in functionNotes
void f(int arr[])void f(int* arr)array size is not preserved
void f(int* arr)pointer parametersame as above
void f(int (&arr)[5])reference to array of 5 intssize 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.

Take Quiz

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.
  • arr usually becomes &arr[0].
  • &arr means address of the whole array.
  • arr and &arr can print similar addresses, but their types differ.
  • In function parameters, int arr[] is treated as int* 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++.

Take Quiz

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) and sizeof(pointer).
  • Demonstrate how p + 1 and q + 1 represent different pointer movements.
Take Quiz

Keep learning

Related questions

View more