What is array to pointer decay? Is there any relation to array pointers?
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.
Arrays are basically the same as pointers in C/C++, but not quite. Once you convert an array:
const int a[] = { 2, 3, 5, 7, 11 };
into a pointer (which works without casting, and therefore can happen unexpectedly in some cases):
const int* p = a;
you lose the ability of the sizeof operator to count elements in the array:
assert( sizeof(p) != sizeof(a) ); // sizes are not equal
This lost ability is referred to as "decay".
For more details, check out this article about array decay.
Here's what the standard says (C99 6.3.2.1/3 - Other operands - Lvalues, arrays, and function designators):
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.
This means that pretty much anytime the array name is used in an expression, it is automatically converted to a pointer to the 1st item in the array.
Note that function names act in a similar way, but function pointers are used far less and in a much more specialized way that it doesn't cause nearly as much confusion as the automatic conversion of array names to pointers.
The C++ standard (4.2 Array-to-pointer conversion) loosens the conversion requirement to (emphasis mine):
An lvalue or rvalue of type “array of N T” or “array of unknown bound of T” can be converted to an rvalue
of type “pointer to T.”
So the conversion doesn't have to happen like it pretty much always does in C (this lets functions overload or templates match on the array type).
This is also why in C you should avoid using array parameters in function prototypes/definitions (in my opinion - I'm not sure if there's any general agreement). They cause confusion and are a fiction anyway - use pointer parameters and the confusion might not go away entirely, but at least the parameter declaration isn't lying.
"Decay" refers to the implicit conversion of an expression from an array type to a pointer type. In most contexts, when the compiler sees an array expression it converts the type of the expression from "N-element array of T" to "pointer to T" and sets the value of the expression to the address of the first element of the array. The exceptions to this rule are when an array is an operand of either the sizeof or & operators, or the array is a string literal being used as an initializer in a declaration.
Assume the following code:
char a[80];
strcpy(a, "This is a test");
The expression a is of type "80-element array of char" and the expression "This is a test" is of type "15-element array of char" (in C; in C++ string literals are arrays of const char). However, in the call to strcpy(), neither expression is an operand of sizeof or &, so their types are implicitly converted to "pointer to char", and their values are set to the address of the first element in each. What strcpy() receives are not arrays, but pointers, as seen in its prototype:
char *strcpy(char *dest, const char *src);
This is not the same thing as an array pointer. For example:
char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;
Both ptr_to_first_element and ptr_to_array have the same value; the base address of a. However, they are different types and are treated differently, as shown below:
a[i] == ptr_to_first_element[i] == (*ptr_to_array)[i] != *ptr_to_array[i] != ptr_to_array[i]
Remember that the expression a[i] is interpreted as *(a+i) (which only works if the array type is converted to a pointer type), so both a[i] and ptr_to_first_element[i] work the same. The expression (*ptr_to_array)[i] is interpreted as *(*a+i). The expressions *ptr_to_array[i] and ptr_to_array[i] may lead to compiler warnings or errors depending on the context; they'll definitely do the wrong thing if you're expecting them to evaluate to a[i].
sizeof a == sizeof *ptr_to_array == 80
Again, when an array is an operand of sizeof, it's not converted to a pointer type.
sizeof *ptr_to_first_element == sizeof (char) == 1
sizeof ptr_to_first_element == sizeof (char *) == whatever the pointer size
is on your platform
ptr_to_first_element is a simple pointer to char.
Arrays, in C, have no value.
Wherever the value of an object is expected but the object is an array, the address of its first element is used instead, with type pointer to (type of array elements).
In a function, all parameters are passed by value (arrays are no exception). When you pass an array in a function it "decays into a pointer" (sic); when you compare an array to something else, again it "decays into a pointer" (sic); ...
void foo(int arr[]);
Function foo expects the value of an array. But, in C, arrays have no value! So foo gets instead the address of the first element of the array.
int arr[5];
int *ip = &(arr[1]);
if (arr == ip) { /* something; */ }
In the comparison above, arr has no value, so it becomes a pointer. It becomes a pointer to int. That pointer can be compared with the variable ip.
In the array indexing syntax you are used to seeing, again, the arr is 'decayed to a pointer'
arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */
The only times an array doesn't decay into a pointer are when it is the operand of the sizeof operator, or the & operator (the 'address of' operator), or as a string literal used to initialize a character array.
It's when array rots and is being pointed at ;-)
Actually, it's just that if you want to pass an array somewhere, but the pointer is passed instead (because who the hell would pass the whole array for you), people say that poor array decayed to pointer.
Array decaying means that, when an array is passed as a parameter to a function, it's treated identically to ("decays to") a pointer.
void do_something(int *array) {
// We don't know how big array is here, because it's decayed to a pointer.
printf("%i\n", sizeof(array)); // always prints 4 on a 32-bit machine
}
int main (int argc, char **argv) {
int a[10];
int b[20];
int *c;
printf("%zu\n", sizeof(a)); //prints 40 on a 32-bit machine
printf("%zu\n", sizeof(b)); //prints 80 on a 32-bit machine
printf("%zu\n", sizeof(c)); //prints 4 on a 32-bit machine
do_something(a);
do_something(b);
do_something(c);
}
There are two complications or exceptions to the above.
First, when dealing with multidimensional arrays in C and C++, only the first dimension is lost. This is because arrays are layed out contiguously in memory, so the compiler must know all but the first dimension to be able to calculate offsets into that block of memory.
void do_something(int array[][10])
{
// We don't know how big the first dimension is.
}
int main(int argc, char *argv[]) {
int a[5][10];
int b[20][10];
do_something(a);
do_something(b);
return 0;
}
Second, in C++, you can use templates to deduce the size of arrays. Microsoft uses this for the C++ versions of Secure CRT functions like strcpy_s, and you can use a similar trick to reliably get the number of elements in an array.
tl;dr: When you use an array you've defined, you'll actually be using a pointer to its first element.
Thus:
When you write arr[idx] you're really just saying *(arr + idx).
functions never really take arrays as parameters, only pointers - either directly, when you specify an array parameter, or indirectly, if you pass a reference to an array.
Sort-of exceptions to this rule:
You can pass fixed-length arrays to functions within a struct.
sizeof() gives the size taken up by the array, not the size of a pointer.
Try this code
void f(double a[10]) {
printf("in function: %d", sizeof(a));
printf("pointer size: %d\n", sizeof(double *));
}
int main() {
double a[10];
printf("in main: %d", sizeof(a));
f(a);
}
and you will see that the size of the array inside the function is not equal to the size of the array in main, but it is equal to the size of a pointer.
You probably heard that "arrays are pointers", but, this is not exactly true (the sizeof inside main prints the correct size). However, when passed, the array decays to pointer. That is, regardless of what the syntax shows, you actually pass a pointer, and the function actually receives a pointer.
In this case, the definition void f(double a[10] is implicitly transformed by the compiler to void f(double *a). You could have equivalently declared the function argument directly as *a. You could have even written a[100] or a[1], instead of a[10], since it is never actually compiled that way (however, you shouldn't do it obviously, it would confuse the reader).
Arrays are automatically passed by pointer in C. The rationale behind it can only be speculated.
int a[5], int *a and int (*a)[5] are all glorified addresses meaning that the compiler treats arithmetic and deference operators on them differently depending on the type, so when they refer to the same address they are not treated the same by the compiler. int a[5] is different to the other 2 in that the address is implicit and does not manifest on the stack or the executable as part of the array itself, it is only used by the compiler to resolve certain arithmetic operations, like taking its address or pointer arithmetic. int a[5] is therefore an array as well as an implicit address, but as soon as you talk about the address itself and place it on the stack, the address itself is no longer an array, and can only be a pointer to an array or a decayed array i.e. a pointer to the first member of the array.
For instance, on int (*a)[5], the first dereference on a will produce an int * (so the same address, just a different type, and note not int a[5]), and pointer arithmetic on a i.e. a+1 or *(a+1) will be in terms of the size of an array of 5 ints (which is the data type it points to), and the second dereference will produce the int. On int a[5] however, the first dereference will produce the int and the pointer arithmetic will be in terms of the size of an int.
To a function, you can only pass int * and int (*)[5], and the function casts it to whatever the parameter type is, so within the function you have a choice whether to treat an address that is being passed as a decayed array or a pointer to an array (where the function has to specify the size of the array being passed). If you pass a to a function and a is defined int a[5], then as a resolves to an address, you are passing an address, and an address can only be a pointer type. In the function, the parameter it accesses is then an address on the stack or in a register, which can only be a pointer type and not an array type -- this is because it's an actual address on the stack and is therefore clearly not the array itself.
You lose the size of the array because the type of the parameter, being an address, is a pointer and not an array, which does not have an array size, as can be seen when using sizeof, which works on the type of the value being passed to it. The parameter type int a[5] instead of int *a is allowed but is treated as int * instead of disallowing it outright, though it should be disallowed, because it is misleading, because it makes you think that the size information can be used, but you can only do this by casting it to int (*a)[5], and of course, the function has to specify the size of the array because there is no way to pass the size of the array because the size of the array needs to be a compile-time constant.
I might be so bold to think there are four (4) ways to pass an array as the function argument. Also here is the short but working code for your perusal.
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
using namespace std;
// test data
// notice native array init with no copy aka "="
// not possible in C
const char* specimen[]{ __TIME__, __DATE__, __TIMESTAMP__ };
// ONE
// simple, dangerous and useless
template<typename T>
void as_pointer(const T* array) {
// a pointer
assert(array != nullptr);
} ;
// TWO
// for above const T array[] means the same
// but and also , minimum array size indication might be given too
// this also does not stop the array decay into T *
// thus size information is lost
template<typename T>
void by_value_no_size(const T array[0xFF]) {
// decayed to a pointer
assert( array != nullptr );
}
// THREE
// size information is preserved
// but pointer is asked for
template<typename T, size_t N>
void pointer_to_array(const T (*array)[N])
{
// dealing with native pointer
assert( array != nullptr );
}
// FOUR
// no C equivalent
// array by reference
// size is preserved
template<typename T, size_t N>
void reference_to_array(const T (&array)[N])
{
// array is not a pointer here
// it is (almost) a container
// most of the std:: lib algorithms
// do work on array reference, for example
// range for requires std::begin() and std::end()
// on the type passed as range to iterate over
for (auto && elem : array )
{
cout << endl << elem ;
}
}
int main()
{
// ONE
as_pointer(specimen);
// TWO
by_value_no_size(specimen);
// THREE
pointer_to_array(&specimen);
// FOUR
reference_to_array( specimen ) ;
}
I might also think this shows the superiority of C++ vs C. At least in reference (pun intended) of passing an array by reference.
Of course there are extremely strict projects with no heap allocation, no exceptions and no std:: lib. C++ native array handling is mission critical language feature, one might say.
This question already has answers here:
Is an array name a pointer?
(8 answers)
Closed 5 years ago.
#include<iostream>
using namespace std;
int main()
{
char s[] = "Hello";
char **a = &s; //Give compilation error
cout<<a;
}
Since s is a pointer to first element, I should be able to store its address in a pointer to character pointer variable..but it is showing an error.
Using the array s can make it decay to a pointer to its first element.
Using plain s when a pointer is wanted is equal to doing &s[0]. That pointer will have the type char *. The expression &s is a pointer to the array and it will have the type char (*)[6]. It's very different.
The expression &s is a pointer to an object of type char[6] which is the type of the array s.
On the other hand the type char ** is pointer type to an object of type char * . The types char[6] and char * are different types. So the pointer types are incompatible.
Your fallacy is that you think that in this expression &s the array designator is implicitly converted to pointer to its first element. However such an assumption is wrong.
It is more explicitly expressed in the C Standard (6.3.2.1 Lvalues, arrays, and function designators) than in the C++ Standard
3 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.
#include <iostream>
using namespace std;
void printarray (int arg[], int length) {
for (int n = 0; n < length; n++) {
cout << arg[n] << " ";
cout << "\n";
}
}
int main ()
{
int firstarray[] = {5, 10, 15};
int secondarray[] = {2, 4, 6, 8, 10};
printarray(firstarray, 3);
printarray(secondarray, 5);
return 0;
}
This code works, but I want to understand how is the array being passed.
When a call is made to the printarray function from the main function, the name of the array is being passed. The name of the array refers to the address of the first element of the array. How does this equate to int arg[]?
The syntaxes
int[]
and
int[X] // Where X is a compile-time positive integer
are exactly the same as
int*
when in a function parameter list (I left out the optional names).
Additionally, an array name decays to a pointer to the first element when passed to a function (and not passed by reference) so both int firstarray[3] and int secondarray[5] decay to int*s.
It also happens that both an array dereference and a pointer dereference with subscript syntax (subscript syntax is x[y]) yield an lvalue to the same element when you use the same index.
These three rules combine to make the code legal and work how you expect; it just passes pointers to the function, along with the length of the arrays which you cannot know after the arrays decay to pointers.
I just wanna add this, when you access the position of the array like
arg[n]
is the same as
*(arg + n)
than means an offset of n starting from de arg address.
so arg[0] will be *arg
The question has already been answered, but I thought I'd add an answer with more precise terminology and references to the C++ standard.
Two things are going on here, array parameters being adjusted to pointer parameters, and array arguments being converted to pointer arguments. These are two quite different mechanisms, the first is an adjustment to the actual type of the parameter, whereas the other is a standard conversion which introduces a temporary pointer to the first element.
Adjustments to your function declaration:
dcl.fct#5:
After determining the type of each parameter, any parameter of type “array of T” (...) is adjusted to be “pointer to T”.
So int arg[] is adjusted to be int* arg.
Conversion of your function argument:
conv.array#1
An lvalue or rvalue of type “array of N T” or “array of unknown bound of T” can be converted to a prvalue of type “pointer to T”. The temporary materialization conversion is applied. The result is a pointer to the first element of the array.
So in printarray(firstarray, 3);, the lvalue firstarray of type "array of 3 int" is converted to a prvalue (temporary) of type "pointer to int", pointing to the first element.
firstarray and secondarray are converted to a pointer to int, when passed to printarray().
printarray(int arg[], ...) is equivalent to printarray(int *arg, ...)
However, this is not specific to C++. C has the same rules for passing array names to a function.
The simple answer is that arrays are ALWAYS passed by reference and the int arg[] simply lets the compiler know to expect an array
#include <iostream>
using namespace std;
void printarray (int arg[], int length) {
for (int n = 0; n < length; n++) {
cout << arg[n] << " ";
cout << "\n";
}
}
int main ()
{
int firstarray[] = {5, 10, 15};
int secondarray[] = {2, 4, 6, 8, 10};
printarray(firstarray, 3);
printarray(secondarray, 5);
return 0;
}
This code works, but I want to understand how is the array being passed.
When a call is made to the printarray function from the main function, the name of the array is being passed. The name of the array refers to the address of the first element of the array. How does this equate to int arg[]?
The syntaxes
int[]
and
int[X] // Where X is a compile-time positive integer
are exactly the same as
int*
when in a function parameter list (I left out the optional names).
Additionally, an array name decays to a pointer to the first element when passed to a function (and not passed by reference) so both int firstarray[3] and int secondarray[5] decay to int*s.
It also happens that both an array dereference and a pointer dereference with subscript syntax (subscript syntax is x[y]) yield an lvalue to the same element when you use the same index.
These three rules combine to make the code legal and work how you expect; it just passes pointers to the function, along with the length of the arrays which you cannot know after the arrays decay to pointers.
I just wanna add this, when you access the position of the array like
arg[n]
is the same as
*(arg + n)
than means an offset of n starting from de arg address.
so arg[0] will be *arg
The question has already been answered, but I thought I'd add an answer with more precise terminology and references to the C++ standard.
Two things are going on here, array parameters being adjusted to pointer parameters, and array arguments being converted to pointer arguments. These are two quite different mechanisms, the first is an adjustment to the actual type of the parameter, whereas the other is a standard conversion which introduces a temporary pointer to the first element.
Adjustments to your function declaration:
dcl.fct#5:
After determining the type of each parameter, any parameter of type “array of T” (...) is adjusted to be “pointer to T”.
So int arg[] is adjusted to be int* arg.
Conversion of your function argument:
conv.array#1
An lvalue or rvalue of type “array of N T” or “array of unknown bound of T” can be converted to a prvalue of type “pointer to T”. The temporary materialization conversion is applied. The result is a pointer to the first element of the array.
So in printarray(firstarray, 3);, the lvalue firstarray of type "array of 3 int" is converted to a prvalue (temporary) of type "pointer to int", pointing to the first element.
firstarray and secondarray are converted to a pointer to int, when passed to printarray().
printarray(int arg[], ...) is equivalent to printarray(int *arg, ...)
However, this is not specific to C++. C has the same rules for passing array names to a function.
The simple answer is that arrays are ALWAYS passed by reference and the int arg[] simply lets the compiler know to expect an array