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 a1, 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"