As ameyCU explained, the []
subscript operator has higher precedence than the unary *
operator, so the expression *a[i]
will be parsed as *(a[i])
; IOW, you're indexing into a
and dereferencing the result.
This works if a
is an array of T
(or a pointer to T
; more on that below). However, if a
is a pointer to an array of T
, that won't do what you want. This is probably best explained visually.
Assume the declarations:
int arr[3] = { 0, 1, 2 };
int (*parr)[3] = &arr; // type of &arr is int (*)[3], not int **
Here's what things look like in memory (sort of; addresses are pulled out of thin air):
Address Item Memory cell
------- ---- -----------
+---+
0x8000 arr: | 0 | <--------+
+---+ |
0x8004 | 1 | |
+---+ |
0x8008 | 2 | |
+---+ |
... |
+---+ |
0x8080 parr: | | ----------+
+---+
...
So you see the array arr
with its three elements, and the pointer parr
pointing to arr
. We want to access the second element of arr
(value 1
at address 0x8004
) through the pointer parr
. What happens if we write *parr[1]
?
First of all, remember that the expression a[i]
is defined as *(a + i)
; that is, given a pointer value a
1, offset i
elements (not bytes) from a
and dereference the result. But what does it mean to offset i
elements from a
?
Pointer arithmetic is based on the size of the pointed-to type; if p
is a pointer to T
, then p+1
will give me the location of the next object of type T
. So, if p
points to an int
object at address 0x1000
, then p+1
will give me the address of the int
object following p
- 0x1000 + sizeof (int)
.
So, if we write parr[1]
, what does that give us? Since parr
points to a 3-element array if int
, parr + 1
will give us the address of the next 3-element array of int
- 0x8000 + sizeof (int [3])
, or 0x800c
(assuming 4-byte int
type).
Remember from above that []
has higher precedence than unary *
, so the expression *parr[1]
will be parsed as *(parr[1])
, which evaluates to *(0x800c)
.
That's not what we want. To access arr[1]
through parr
, we must make sure parr
has been dereferenced before the subscript operation is applied by explicitly grouping the *
operator with parentheses: (*parr)[1]
. *parr
evaluates to 0x8000
which has type "3-element array of int
"; we then access the second element of that array (0x8000 + sizeof (int)
, or 0x8004
) to get the desired value.
Now, let's look at something - if a[i]
is equivalent to *(a+i)
, then it follows that a[0]
is equivalent to *a
. That means we can write (*parr)[1]
as (parr[0])[1]
, or just parr[0][1]
. Now, you don't want to do that for this case since parr
is just a pointer to a 1D array, not a 2D array. But this is how 2D array indexing works. Given a declaration like T a[M][N];
, the expression a
will "decay" to type T (*)[N]
in most circumstances. If I wrote something like
int arr[3][2] = {{1,2},{3,4},{5,6}};
int (*parr)[2] = arr; // don't need the & this time, since arr "decays" to type
// int (*)[2]
then to access an element of arr
through parr
, all I need to do is write parr[i][j]
; parr[i]
implicitly dereferences the parr
pointer.
- This is where things get confusing; arrays are not pointers, and they don't store any pointers internally. Instead, of an array expression is not the operand of the
sizeof
or unary *
operators, its type is converted from "N-element array of T
" to "pointer to T
", and the value of the expression is the address of the first element of the array. This is why you can use the []
operator on both array and pointer objects.
This is also why we used the &
operator to get the address of arr in our code snippet; if it's not the operand of the `&` operator, the expression "decays" from type "3-element array of int
" to "pointer to int
"