In C++, when I have to use an array inside some function, I pass the array as an argument and get a pointer pointing to the first element of the array. While it is okay to use, and not much of a hassle to use a pointer, I was wondering if there exists some in-built header file, or any other set of instructions, by which when I want to access the i-th element of the array I can simply write array[i] and it gets read by the compiler as *(array+i)?
It would be great if one exists, since it would make it quite uniform and easy to code, since all the times when I use vectors I can access the i-th element just by vector[i] while in array I have to use it the other way *(array+i).
Also, is there some reason why the developers of C++ chose to return pointers to an array instead of the object itself?
If a is a pointer and i has an integral type, then a[i] is always the same as *(a+i). There is no need to include a header or anything to make it work.
I was wondering if there exists some in-built header file or any other set of instructions by which when I want to access the i-th element of the array I can simply write array[i] and it gets read by the compiler as *(array+i)
No, there is no such header or set of instructions, because it is part of the language.
For a pointer and an integral type, a[i] means *(a+i). This is such a strong statement that:
int base_array[3]={1,2,3}; // an array of 3 elements
int* ptr_array = base_array; // base_array "decays" to a pointer to the first element
std::cout << 2[ptr_array] << "\n"; // huh?!?!
prints 3, because *(2+ptr_array) is 3; ie, it even works backwards.
Can we somehow use array[i] instead of *(array+i)?
Yes, we can. Those expressions are practically identical. The subscript operator is much more readable, so I recommend using that.
I was wondering if there exists some in-built header file
You don't need to include any header.
while in array i have to use it the other way *(array+i)
Just because you can, doesn't mean that you have to. You don't have to use it the other way.
P.S. Besides arrays and vectors, we can also use the subscript operator with a pointer to element of an array.
Also, is there some reason why the developers of C++ chose to return pointers to an array instead of the object itself?
Because sometimes indirection is necessary or useful.
From the C++ 14 Standard (5.2.1 Subscripting)
1 A postfix expression followed by an expression in square brackets is
a postfix expression. One of the expressions shall have the type
“array of T” or “pointer to T” and the other shall have unscoped
enumeration or integral type. The result is of type “T.” The type “T”
shall be a completely-defined object type. The expression E1[E2] is
identical (by definition) to *((E1)+(E2)) [ Note: see 5.3 and 5.7
for details of * and + and 8.3.4 for details of arrays. — end note ],
except that in the case of an array operand, the result is an lvalue
if that operand is an lvalue and an xvalue otherwise.
So these expressions array[i] and *( array + i ) are evaluated the same way whether array in this expressions is an array designator or a pointer to first element of an array,
Moreover these expressions array[i] and i[array] are also evaluated the same way.
Arrays are non-modifianle lvalues so you may not return an array from a function. If to use an array designator in a return statement it will be converted to pointer to its first element. As a result arrays do not have the copy assignment operator.
On the other hand you can return a reference to an array provided that it doesn not have automatic storage duration.
For example
#include <iostream>
const size_t N = 5;
decltype( auto ) f( int ( &a )[N], int init )
{
for ( size_t i = 0; i < N; i++ )
{
a[i] = init++;
}
return a;
}
int main()
{
int a[N];
decltype( auto ) ra = f( a, 0 );
std::cout << sizeof( ra ) << '\n';
for ( const auto &item : ra )
{
std::cout << item << ' ';
}
std::cout << '\n';
return 0;
}
The program output is
20
0 1 2 3 4
Related
Regular static allocated array looks like this, and may be accessed using the following formulas:
const int N = 3;
const int M = 3;
int a1[N][M] = { {0,1,2}, {3,4,5}, {6,7,8} };
int x = a1[1][2]; // x = 5
int y = *(a1+2+N*1); // y = 5, this is what [] operator is doing in the background
Array is continuous region of memory. It looks different in case of dynamic array allocation, there is array of pointer to arrays instead:
int** a2 = new int*[N];
for (int i = 0; i < N; i++)
a2[i] = new int[M];
//Assignment of values as in previous example
int x = a2[1][2];
int y = *(*(a2+1))+2); // This is what [] operator is doing in the background, it needs to dereference pointers twice
As we can see, operations done by [] operator are completely different in case of typical continuous array and dynamically allocated array.
My questions are now following:
Is my understanding of [] operations correct?
How C/C++ compiler can distinguish which [] operation it should perform, and where it's implemented? I can image implementing it myself in C++ by overloading [] operator, but how C/C++ treat this?
Will it work correctly in C language using malloc instead of new? I don't see any reasons why not actually.
For this declaration of an array
int a1[N][M] = { {0,1,2}, {3,4,5}, {6,7,8} };
these records
int x = a1[1][2];
int y = *(a1+2+N*1);
are not equivalent.
The second one is incorrect. The expression *(a1+2+N*1) has the type int[3] that is implicitly converted to an object of the type int * used as an initializer. So the integer variable y is initialized by a pointer.
The operator a1[1] is evaluated like *( a1 + 1 ) . The result is a one-dimensional array of the type int[3].
So applying the second subscript operator you will get *( *( a1 + 1 ) + 2 ).
The difference between the expressions when used the two-dimensional array and the dynamically allocated array is that the designator of the two-dimensional array in this expression (a1 + 1) is implicitly converted to a pointer to its first element of the type int ( * )[3] while the pointer to the dynamically allocated array of pointers still have the same type int **.
In the first case dereferencing the expression *(a1 + 1 ) you will get lvalue of the type int[3] that in turn used in the expression *( a1 + 1) + 2 is again implicitly converted to a pointer of the type int *.
In the second case the expression *(a1 + 1) yields an object of the type int *.
In the both cases there is used the pointer arithmetic. The difference is that when you are using arrays in the subscript operator then they are implicitly converted to pointers to their first elements.
When you are allocating dynamically arrays when you are already deals with pointers to their first elements.
For example instead of these allocations
int** a2 = new int*[N];
for (int i = 0; i < N; i++)
a2[i] = new int[M];
you could just write
int ( *a2 )[M] = new int[N][M];
Is my understanding of [] operations correct?
int y = *(a1+2+N*1); // y = 5, this is what [] operator is doing in the background
By definition, the way to translate the subscript operators to the corresponding indirection and pointer arithmetic is:
int y = *(*(a1+1)+2)
Which is exactly the same as in the case of int**.
How C/C++ compiler can distinguish which [] operation it should perform
The compiler uses the type system. It knows the types of the expressions and it knows what subscript operation means for each type.
Will it work correctly in C language using malloc instead of new? I don't see any reasons why not actually.
It doesn't matter how an array is created. Subscript operator works the same way with all pointers.
a1 and a2 are different types, and as such, the behavior of operator [] will depend on how that type defines the operator. In this case you're dealing with intrinsic compiler behaviors that conform to the C++ spec, but it could just as well be a std::unique_ptr<>, or MyClass with overloaded operator[]
Each operation leads to result of some specific type. Each type defines what kind of operation is available for it.
Note that array has ability to decay to pointer to element of array. So some_array + int_value leads to pointer to element.
Here is code which exposes types of each step: https://godbolt.org/z/jeKWh5WWW
#include <type_traits>
const int N = 3;
const int M = 4;
int a1[N][M] = { {0,1,2,0}, {3,4,5,0}, {6,7,8,0} };
int** a2 = new int*[N];
static_assert(
std::is_same_v<decltype(a1[0][0]), int&>,
"value type is reference to int");
static_assert(
std::is_same_v<decltype(a1[0]), int(&)[M]>,
"row type is reference to int aray");
static_assert(
std::is_same_v<decltype(a1 + 1), int(*)[M]>,
"advanced pointer is pointer to array of ints");
static_assert(
!std::is_same_v<decltype(a1[0]), int*&>,
"row type is reference to int pointer");
static_assert(
std::is_same_v<decltype(a2[0][0]), int&>,
"value type is reference to int");
static_assert(
!std::is_same_v<decltype(a2[0]), int(&)[M]>,
"row type is not reference to int aray");
static_assert(
std::is_same_v<decltype(a2 + 1), int**>,
"advanced pointer is pointer to pointer to int");
static_assert(
std::is_same_v<decltype(a2[0]), int*&>,
"row type is reference to int pointer");
I think this is good appendix to other answers.
How C/C++ compiler can distinguish which [] operation it should perform, and where it's implemented?
The built-in [] operator (that is, not a user-defined overload) always does one thing: It adds its two operands and dereference the results. E1[E2] is defined to be (*((E1)+(E2))). Here is how this works:
If E1 or E2 is an array, it is automatically converted to a pointer to its first element. This not a part of the [] operator per se; it is a built-in part of the C and C++ languages. In C, the specific rule is that, whenever an array is used in an expression other than as the operand of sizeof, the operand of unary &, or as a string literal used to initialize an array, it is converted to a pointer to its first element.
Thus, whether the code is written with a pointer or an array, [] always has a pointer operand. You may write an array, but [] always receives a pointer.
The + operator adds an integer to a pointer by adjusting the pointer by the given number of elements: Given a pointer to element j of an array and an integer k to add to it, it produces a pointer to element j+k of the array.
From the pointer to an element, the * operator produces an lvalue for the referenced element.
The combination of automatic array conversion, +, and *, means that A[i] produces an lvalue for element i of the array A.
Here is how this works for the expression A[i][j] where A is an array declared as SomeType A[m][n]:
In A[i][j], A is an array of m arrays of n elements. It is automatically converted to a pointer to its first element (the one with index 0).
Then A[i] produces an lvalue for element i of this array. In other words, the result of A[i] is an array; it is an array of n SomeType objects.
Since the result of A[i] is an array, it is automatically converted to a pointer to its first element.
Then A[i][j] produces an lvalue for element j of that array.
Since the pointer arithmetic operates in units of the pointed-to-type, it includes the scaling for the sizes of the elements. This is what makes the calculation of A[i] scaled by the size of the subarray of n elements.
Will it work correctly in C language using malloc instead of new? I don't see any reasons why not actually.
Sure, if done correctly.
Lets say we create an array like:
int a[4]={1,2,3,4};
Now a is the name of this array and also the pointer points to the first element a[0]. So when I want to call the elements in the array, I can use a[ i ] or *(a+i).
Now I have a function:
void print_array(int* array, int arraySize){
for(int i=0; i<arraySize; i++){
cout<<*(array+i)<<endl;
cout<<array[i]<<endl;
}
}
When I pass a[4]={1,2,3,4} into this function using print_array(a,4), for the first line of cout, I fully understand because I use *(a+i) method to access data and a is the pointer I passed.
What I can't understand is: since I pass a pointer a into function, why can I use a in the format of a[i] like the second line of cout? Isn't a a pointer? If a is a pointer why does a[i] work?
This has confused me for a whole day. Any help will be much appreciated!
a is an array, not a pointer. They are not the same things. However, the name a can be implicitly converted to a pointer (with the value &a[0]).
For example;
int main()
{
int a[] = {1,2,3,4};
int *p = a; // p now has the value &a[0]
Now, after this partial code snippet, assuming i is an integral value, rules of the language amount to;
a[i] is equivalent to *(a + i) which is equivalent to *(&a[0] + i)
p[i] is equivalent to *(p + i)
Now, since p is equal to &a[0] this means that a[i], *(a + i), p[i], and *(p + i) are all equivalent.
When calling print_arrat(a, 4) where a is the name of an array, then a is ALWAYS converted to a pointer. This means print_arrat() is always passed a pointer. And this means *(array + i) inside print_arrat() is the same as a[i] in the caller.
This quote from the C++ Standard will make the point clear (5.2.1 Subscripting)
1 A postfix expression followed by an expression in square brackets is
a postfix expression. One of the expressions shall have the type
“array of T” or “pointer to T” and the other shall have unscoped
enumeration or integral type. The result is of type “T.” The type “T”
shall be a completely-defined object type.64 The expression E1[E2] is
identical (by definition) to *((E1)+(E2)) [Note: see 5.3 and 5.7 for
details of * and + and 8.3.4 for details of arrays. —end note], except
that in the case of an array operand, the result is an lvalue if that
operand is an lvalue and an xvalue otherwise.
Because in effect, while the subscript operator is defined on arrays, what happens is that they decay into pointers for the arithmetic to occur.
Meaning if a is an array, semantically what happens is:
int b = a[i]; => int *__tmp = a; int b = *(__tmp + i);
However, once operator overloading comes into play, then it is no longer true that a[i] == *(a + i). The right hand side may not even be defined.
What I can't understand is: since I pass a pointer "a" into function, why can I use "a" in the format of a[i] like the second line of "cout"?
Because subscript operator a[i] is defined for arrays and it is equivalent to *(a+i) by definition.
In the line with cout, you use array[i] however, where array is a pointer. This is also allowed, because the subscript operator is also defined for pointers.
Isn't "a" a pointer?
No. a is an array. array is a pointer.
Here is the code I'm having trouble to understand:
char* myPtr = "example";
myPtr[1] = 'x';
How am I allowed to use myPtr[1]? Why can I choose positions like a do on arrays? myPtr is not even an array.
Obs. I know about lookup table, literal pooling and string literals, my concern is just how this even compile. I don't use pointers that much.
Can anyone help?
Apparently you made an assumption that applicability of [] operator to something necessarily implies that that "something" is an array. This is not true. The built-in [] operator has no direct relation to arrays. The [] is just a shorthand for a combination of * and + operators: by definition a[b] means *(a + b), where one operand is required to be a pointer and another is required to be an integer.
Moreover, when you apply the [] operator to an actual array, that array gets implicitly converted to a pointer type first, and only then the resultant pointer can act as an operand of [] operator. This actually means the opposite of what you supposedly assumed initially: operator [] never works with arrays. By the time we get to the [] the array has already decayed to a pointer.
As a related side-note, this latter detail manifests itself in one obscure peculiarity of the first C language standard. In C89/90 the array-to-pointer conversion was not allowed for rvalue arrays, which also prevented the [] operator from working with such arrays
struct S { int a[10]; };
struct S foo(void) { struct S s = { 0 }; return s; }
int main()
{
foo().a[5];
/* ERROR: cannot convert array to pointer, and therefore cannot use [] */
return 0;
}
C99 expanded the applicability of that conversion thus making the above code valid.
It compiles according to §5.2.1/1 [expr.sub] of the C++ standard:
A postfix expression followed by an expression in square brackets is a postfix expression. One of the expressions shall have the type “array of T” or “pointer to T” and the other shall have unscoped enumeration or integral type. The result is of type “T”. The type “T” shall be a completely-defined object type.
The expression E1[E2] is identical (by definition) to *((E1)+(E2)), except that in the case of an array operand, the result is an lvalue if that operand is an lvalue and an xvalue otherwise.
Since "example" has type char const[8] it may decay to char const* (it used to decay to char* as well, but it's mostly a relict of the past) which makes it a pointer.
At which point the expression myPtr[1] becomes *(myPtr + 1) which is well defined.
Pointers hold the address of memory location of variables of specific data types they are assigned to hold. As others have pointed out its counter-intuitive approach take a bit of learning curve to understand.
Note that the string "example" itself is immutable however, the compiler doesn't prevent the manipulation of the pointer variable, whose new value is changed to address of string 'x' (this is not same as the address of x in 'example'),
char* myPtr = "example";
myPtr[1] = 'x';
Since myPtr is referencing immutable data when the program runs it will crash, though it compiles without issues.
From C perspective, here, you are dereferencing a mutable variable.
By default in C, the char pointer is defined as mutable, unless specifically stated as immutable through keyword const, in which case the binding becomes inseparable and hence you cannot assign any other memory address to the pointer variable after defining it.
Lets say your code looked like this,
const char *ptr ="example";
ptr[1] = 'x';
Now the compilation will fail and you cannot modify the value as this pointer variable is immutable.
You should use char pointer only to access the individual character in a string of characters.
If you want to do string manipulations then I suggest you declare an int to store each character's ASCII values from the standard input output like mentioned here,
#include<stdio.h>
int main()
{
int countBlank=0,countTab=0,countNewLine=0,c;
while((c=getchar())!=EOF)
{
if(c==' ')
++countBlank;
else if(c=='\t')
++countTab;
else if(c=='\n')
++countNewLine;
putchar(c);
}
printf("Blanks = %d\nTabs = %d\nNew Lines = %d",countBlank,countTab,countNewLine);
}
See how the integer takes ASCII values in order to get and print individual characters using getchar() and putchar().
A special thanks to Keith Thompson here learnt some useful things today.
The most important thing to remember is this:
Arrays are not pointers.
But there are several language rules in both C and C++ that can make it seem as if they're the same thing. There are contexts in which an expression of array type or an expression of pointer type is legal. In those contexts, the expression of array type is implicitly converted to yield a pointer to the array's initial element.
char an_array[] = "hello";
const char *a_pointer = "goodbye";
an_array is an array object, of type char[6]. The string literal "hello" is used to initialize it.
a_pointer is a pointer object, of type const char*. You need the const because the string literal used to initialize it is read-only.
When an expression of array type (usually the name of an array object) appears in an expression, it is usually implicitly converted to a pointer to its initial (0th) element. So, for example, we can write:
char *ptr = an_array;
an_array is an array expression; it's implicitly converted to a char* pointer. The above is exactly equivalent to:
char *ptr = &(an_array[0]); // parentheses just for emphasis
There are 3 contexts in which an array expression is not converted to a pointer value:
When it's the operand of the sizeof operator. sizeof an_array yields the size of the array, not the size of a pointer.
When it's the operand of the unary & operator. &an_array yields the address of the entire array object, not the address of some (nonexistent) char* pointer object. It's of type "pointer to array of 6 chars", or char (*)[6].
When it's a string literal used as an initializer for an array object. In the example above:
char an_array[] = "hello";
the contents of the string literal "hello" are copied into an_array; it doesn't decay to a pointer.
Finally, there's one more language rule that can make it seem as if arrays were "really" pointer: a parameter defined with an array type is adjusted so that it's really of pointer type. You can define a function like:
void func(char param[10]);
and it really means:
void func(char *param);
The 10 is silently ignored.
The [] indexing operator requires two operands, a pointer and an integer. The pointer must point to an element of an array object. (A standalone object is treated as a 1-element array.) The expression
arr[i]
is by definition equivalent to
*(arr + i)
Adding an integer to a pointer value yields a new pointer that's advanced i elements forward in the array.
Section 6 of the comp.lang.c FAQ has an excellent explanation of all this stuff. (It applies to C++ as well as to C; the two languages have very similar rules in this area.)
In C++, your code generates a warning during compile:
{
//char* myPtr = "example"; // ISO C++ forbids converting a string
// constant to ‘char*’ [-Wpedantic]
// instead you should use the following form
char myPtr[] = "example"; // a c-style null terminated string
// the myPtr symbol is also treated as a char*, and not a const char*
myPtr[1] = 'k'; // still works,
std::cout << myPtr << std::endl; // output is 'ekample'
}
On the other hand, std::string is much more flexible, and has many more features:
{
std::string myPtr = "example";
myPtr[1] = 'k'; // works the same
// then, to print the corresponding null terminated c-style string
std::cout << myPtr.c_str() << std::endl;
// ".c_str()" is useful to create input to system calls requiring
// null terminated c-style strings
}
The semantics of abc[x] is "Add x*sizeof(type)" to abc where abc is any memory pointer. Arrays variable behave like memory pointers and they just point to beginning of the memory location allocated to array.
Hence adding x to array or pointer variable both will point to memory which is same as variable pointing to + x*sizeof(type which array contains or pointer points to, e.g. in case of int pointers or int array it's 4)
Array variables are not same as pointer as said in comment by Keith as array declaration will create fix sized memory block and any arithmetic on that will use size of array not the element types in that array.
I am confused about the output of the following code.
#include<iostream>
#include<cstdlib>
using namespace std;
int main()
{
int a[] = {1,2,3};
cout << a << " " << &a << endl;
cout << sizeof(a) << " " << sizeof(&a) << endl;
return 0;
}
The output is
0xbfcd3ae4 0xbfcd3ae4
12 4
How can a and &a print the same expression but have different sizes?
I always thought that for any array, its name always has the value = address of the first byte.
Also &a should not make sense, since one cannot have an address (obtained with the & operator) to an address(the value of a). Yet the code gives an output and infact 'a == &a'
according to the output.
Similarly why is the output of sizeof(a) = 12 (which is the total memory occupied)
by the array? a being a "pointer" itself sizeof(a) = 4 bytes (on my 32 bit Ubuntu 11.04)
Obviously there is some misconception I am having. Could some one sort this out for me ?
An array is not a pointer, but an array decays to a pointer when you try to use it like one. In your case printing the address of the array automatically converts it into a pointer.
There's little difference between the automatically converted pointer and the one created explicitly with &, except that one is a pointer to a single element while the other is a pointer to the entire array. If you had used &a[0] then they would be identical.
First of all, realize that there is a difference between an object1 and the expression that we use to refer to that object. In your code, a is an expression that refers to an object large enough to store 3 int values.
Except when it is the operand of the sizeof or unary & operators, or is a string literal being used to initialize another array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element of the array.
Given a statement like
cout << a;
the expression a has type "3-element array of int"; since a is not an operand of the sizeof or unary & operators, it will be converted to an expression of type "pointer to int", and the value of the expression will be the address of the first element in the array.
OTOH, given a statement like
cout << &a;
the expression a is the operand of the unary & operator, so the rule doesn't apply; instead, the type of the expression is "pointer to 3-element array of int", and the value of the expression is the address of the array.
In both C and C++, the address of the array and the address of the first element of the array are the same, so both expressions yield the same value, but the types of the two expressions are different (int * vs. int (*)[3]).
In the statement
cout << sizeof a; // or sizeof (a)
the expression a is the operand of the sizeof operator, so again, the conversion rule doesn't apply; instead, the expression sizeof a evaluates to the number of bytes used by the array (in this case, 12).
In the statement
cout << sizeof &a; // or sizeof (&a)
the expression &a evaluates to the address of the array and has type int (*)[3], so sizeof &a evaluates to the number of bytes used by the pointer type (in your case, 4 bytes).
In both C and C++, when you declare an array like
int a[3];
the only storage set aside is for the 3 array elements; there's no separate storage for an a variable that points to the first element of the array (which is why a and &a yield the same value).
1. In the sense of something that occupies memory, not the sense of an instance of a class
In C, the expression a is a primary expression which designates an array object. In a context where this expression is evaluated, it produces a value which is of pointer type, and points to the first element of the array.
This is a special evaluation rule for arrays. It does not mean that a is a pointer. It isn't; a is an array object.
When the sizeof of & operators are applied to a, it is not evaluated. sizeof produces the size of the array, and & takes its address: it produces a pointer to the array as a whole.
The values of the expressions a and &a point to the same location, but have a different type: a pointer to type of the array element (such as pointer to int), versus the type of the array (such pointer to array of 10 int) respectively.
C++ works very similarly in this area for compatibility with C.
There are other situations in C where the value of an expression which denotes a value or object of one type produces a value of another type. If c has type char, then the value of c is a value of type int. Yet &c has type char *, and sizeof c produces 1. Just like an array is not a pointer, c is not an int; it just produces one.
C++ isn't compatible in this area; character expressions like names of char variables or character constants like 'x' have type char. This allows void foo(int) and void foo(char) to be different overloads of a function foo. foo(3) will call the first one, and foo('x') the second.
Look at this following code for better understanding:
#include<iostream>
#include<cstdlib>
using namespace std;
int main()
{
int a[] = {1,2,3};
cout << a << " " << &a << endl;
cout << a+1 << " " << &a+1 << endl;
cout << sizeof(a) << " " << sizeof(&a) << endl;
return 0;
}
Output:
0x7fffb231ced0 0x7fffb231ced0
0x7fffb231ced4 0x7fffb231cedc
12 8
We got same address for a and &a. So we you may think they are same. But that not true, because a variable (a) and its address (&a) cannot be same.
Both printed address, but both are printing different addresses, "a” is a “pointer to the first element of array” but “&array” is a “pointer to whole array”.
That's why when we printed a+1 we got 0x7fffb231ced4which is address to second element in array (and since it array of integers, its increased by 4) while &a+1 got us 0x7fffb231cedc which is increment of 4*3= 12 (c means 12 in hex) because we had 3 elements in array. &arr+1 points to next memory location after array.
Also this fundamental difference is what results in different in sizes of a and &a. size of a is actually the size of array while size of &a is size of pointer.
I have a question about the array name a
int a[10]
How is the array name defined in C++? A constant pointer? It is defined like this or just we can look it like this? What operations can be applied on the name?
The C++ standard defines what an array is and its behaviour. Take a look in the index. It's not a pointer, const or otherwise, and it's not anything else, it's an array.
To see a difference:
int a[10];
int *const b = a;
std::cout << sizeof(a); // prints "40" on my machine.
std::cout << sizeof(b); // prints "4" on my machine.
Clearly a and b are not the same type, since they have different sizes.
In most contexts, an array name "decays" to a pointer to its own first element. You can think of this as an automatic conversion. The result is an rvalue, meaning that it's "just" a pointer value, and can't be assigned to, similar to when a function name decays to a function pointer. Doesn't mean it's "const" as such, but it's not assignable.
So an array "is" a pointer much like a function "is" a function pointer, or a long "is" an int. That is to say, it isn't really, but you can use it as one in most contexts thanks to the conversion.
An array name is not a constant pointer - however it acts like one in so many contexts (it converts to one on sight pretty much) that for most purposes it is.
From 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.