What Is Array to Pointer Decay

What is array to pointer decay?

It's said that arrays "decay" into pointers. A C++ array declared as int numbers [5] cannot be re-pointed, i.e. you can't say numbers = 0x5a5aff23. More importantly the term decay signifies loss of type and dimension; numbers decay into int* by losing the dimension information (count 5) and the type is not int [5] any more. Look here for cases where the decay doesn't happen.

If you're passing an array by value, what you're really doing is copying a pointer - a pointer to the array's first element is copied to the parameter (whose type should also be a pointer the array element's type). This works due to array's decaying nature; once decayed, sizeof no longer gives the complete array's size, because it essentially becomes a pointer. This is why it's preferred (among other reasons) to pass by reference or pointer.

Three ways to pass in an array1:

void by_value(const T* array)   // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])

The last two will give proper sizeof info, while the first one won't since the array argument has decayed to be assigned to the parameter.

1 The constant U should be known at compile-time.

Array to pointer decay and passing multidimensional arrays to functions

You got it slightly wrong: moreThings also decays to a pointer to the first element, but since it is an array of an array of chars, the first element is an "array of 8 chars". So the decayed pointer is of this type:

char (*p)[8] = moreThings;

The value of the pointer is of course the same as the value of &moreThings[0][0], i.e. of the first element of the first element, and also the same of &a, but the type is a different one in each case.

Here's an example if char a[N][3]:

+===========================+===========================+====
|+--------+--------+-------+|+--------+--------+-------+|
|| a[0,0] | a[0,1] | a[0,2]||| a[1,0] | a[1,1] | a[1,2]|| ...
|+--------+--------+-------+++--------+--------+-------++ ...
| a[0] | a[1] |
+===========================+===========================+====
a
^^^
||+-- &a[0,0]
|+-----&a[0]
+-------&a
  • &a: address of the entire array of arrays of chars, which is a char[N][3]

  • &a[0], same as a: address of the first element, which is itself a char[3]

  • &a[0][0]: address of the first element of the first element, which is a char

This demonstrates that different objects may have the same address, but if two objects have the same address and the same type, then they are the same object.

Is the array to pointer decay changed to a pointer object?

"But a itself is not pointing to another region of memory, it IS the region of memory itself.

"So when the compiler converts it to a pointer, does it save it (like p) somewhere in memory or it's an implicit conversion?"

It is an implicit conversion. The compiler does not implement the creation of a separate pointer object in memory (which you can f.e. assign in any manner with a different memory address) to hold the address of the first element.

The standard states (emphasize mine):

"Except when it is the operand of the sizeof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type "array of type" is converted to an expression with type "pointer to type" that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined."

Source: ISO/IEC 9899:2018 (C18), 6.3.2.1/4

The array is converted to an expression of pointer type, it is not an lvalue.

The compiler just evaluates a to &a[0] (pointer to a[0]).



"I understand that array names are converted to pointers."

An array does not always convert to a pointer to its first element. Look at the first part of the quote above. F.e. when used as &a, a does not decay to a pointer to its first element. Rather it gains a pointer to the whole array int (*)[3].

why does the array decay to a pointer in a template function

Because arrays can not be passed by value as a function parameter.

When you pass them by value they decay into a pointer.

In this function:

template <class T>
void f(T buff) {

T can not be char (&buff)[3] as this is a reference. The compiler would have tried char (buff)[3] to pass by value but that is not allowed. So to make it work arrays decay to pointers.

Your second function works because here the array is passed by reference:

template <class T>
void f1(T& buff) {

// Here T& => char (&buff)[3]

Exceptions to array decaying into a pointer?

Sure.

In C99 there are three fundamental cases, namely:

  1. when it's the argument of the & (address-of) operator.

  2. when it's the argument of the sizeof operator.

  3. When it's a string literal of type char [N + 1] or a wide string literal of type wchar_t [N + 1] (N is the length of the string) which is used to initialize an array, as in char str[] = "foo"; or wchar_t wstr[] = L"foo";.

Furthermore, in C11, the newly introduced alignof operator doesn't let its array argument decay into a pointer either.

In C++, there are additional rules, for example, when it's passed by reference.

If arr[3] is an array and ptr is a pointer, then why do arr and &arr give same result but ptr and &ptr don't [duplicate]

Arrays are not pointers!

Arrays do decay to a pointer to their first element in all sorts of circumstances. For example std::cout << arr; actually prints the memory address of the first element of the array. std::cout << &arr; prints the memory address of the array. As the address of the first element is the same as the address of the array you see the same value.

However, just because they have the same value, does not mean they are the same. arr can decay to a int*, while &arr is a pointer to an array, a int(*)[3].

I hope the following will help to clear things up a little:

#include <iostream>
#include <type_traits>


void make_it_decay(int x[]) {
std::cout << std::is_same_v< decltype(x), int*> << "\n";
}

int main() {
int arr[3] = {1,2,3};

//std::cout << (arr == &arr) << "\n"; // does not compile !
std::cout << (arr == &(arr[0])) << "\n";

std::cout << std::is_same_v< decltype(arr), int[3]> << "\n";
std::cout << std::is_same_v< decltype(&arr),int(*)[3]> << "\n";
std::cout << std::is_same_v< decltype(&arr[0]), int* > << "\n";
make_it_decay(arr);
}

output:

1
1
1
1
1

I use decltype to infer the type of certain expressions and std::is_same_v to see if the expressions are of same type.

arr is of type int[3]. It is an array. It is not a pointer.

&arr is the address of the array. It is a pointer to an array with three elements, a int(*)[3].

&arr[0] even though it has the same value as &arr is of different type. It is a pointer to int, an int*.

When we pass arr to a function then it decays to a pointer to the first element of the array. And we can see that inside the function x is int*.


Now to your quesiton...

Above I tried to lay out what happens when you write std::cout << arr. Pointers are different, because ... well arrays are not pointers.

std::cout << ptr;  // prints the value ptr
std::cout << &ptr; // prints the address of ptr

Perhaps some visualization helps. The difference in types gets most apparent when incrementing the pointers

 -------------------
| arr |
-------------------
| 1 | 2 | 3 |
-------------------
^ ^ ^
&arr | &arr + 1
&arr[0] |
&arr[0] + 1

Array reference binding vs. array-to-pointer conversion with templates

The second overload is more specialized than the first one during partial ordering of function templates.

According to [temp.deduct.partial]/5 the reference on T &t of the first overload is ignored during template argument deduction performed for partial ordering. The following paragraphs distinguish based on reference/value category only if both parameters are reference types.

Then T of the first overload can always deduce against a type A* invented from the parameter of the second overload, but T* of the second overload can't deduce against a type A invented from the parameter of the first overload.

Therefore the second overload is more specialized and is chosen.


With T (&t)[4] argument deduction in both directions will fail because deduction of T[4] against A* will fail and so will deduction of T* against A[4]. Array-to-pointer decay of the array type is specified for template argument deduction for a function call but not for template argument deduction for partial ordering. See also active CWG issue 402.

So neither template will be more specialized in this case and the partial ordering tiebreaker does not apply.


The array-to-pointer conversion is not relevant. It is not considered any worse than the identity conversion sequence (see [over.ics.rank]/3.2.1 excluding lvalue transformations which array-to-pointer conversions are).



Related Topics



Leave a reply



Submit