There are many differences. First of all, the types (and note that I'm renaming the variables because you gave them the same name):
double (* p)[2];
double * q[2];
C's declarations with nested parentheses are notoriously confusing, but the rules are generally simple:
- The parentheses take precedence (that's what they are for)
- In absence of them, things to the right take precedence over things to the left
- When dealing with several modifiers next to each other (e.g.,
int p[3][4];
), the closest ones to the variable name take precedence
(They are technically operators and operands, but I'm not calling them that because it's easy to confuse them with actual operators that show up in expressions.)
So let's parse them:
p
is a pointer (the first thing that applies is the *
that is enclosed within it in the parentheses) to a two-element array of doubles.
q
is a two-element array (the [2]
on the right takes precedence over the *
on the left) of pointers to double.
And is there anything else of those types? Well, yes.
void function (double array[2][2], int i, int j) { /* ... */ }
As you probably know, when used in an expression, arrays decay to pointers. If x
is declared int x[20];
, when used in an expression it decays to an int *
. And what about multidimensional arrays? Well, they decay too, but only one layer, of course: a pointer to a pointer isn't the same as a pointer to an array. So something like int y[3][4]
, which is a three-element array of four-element arrays of integers, will decay to a pointer to the same element type: a pointer to four-element arrays of integers. In other words, int (*) [4]
. (The variable name would go inside the parentheses, after the *
. You can use the rules above to figure out the type!)
So, that function argument would decay to double (*) [2]
when used — the same type as p
! And thus, assigning them directly would be perfectly legal:
double (* p)[2] = array;
But since you took an index to the array in your example, you created something invalid. Remember that foo[bar]
is equivalent to *(foo + bar)
in C. Adding an offset to a pointer doesn't change its type, so array + i
would still have the same type — but when you dereference it by doing array[i]
(or equivalently, *(array + i)
), you're removing one layer of indirection and turning the value into a double [2]
, which immediately decays to double *
. That pointer type is incompatible with p
's, and thus you (should) get an error, or at least a warning that you should really pay attention to.
On the other hand, you can make p
point to an element in the (outer) array other than the first:
double (* p)[2] = array + 1;
(Some people prefer doing &array[1]
instead of array + 1
. They are equivalent; choose whichever form you want.)
Now for the other variable. That's a two-element array of pointers. So when you do this:
double * q[2] = {array[i]};
...you're really doing this, since missing entries are zero-initialized:
double * q[2] = {array[i], NULL};
However, this one is perfectly legal and it works, although probably not as you intended. As I said before, array[i]
is of type double *
— and since this is an array of double *
elements, the assignment works without errors. So now you have q
as a two-element array, where the first element points into the i
'th row of array
and the second element is a null pointer. It's valid, but almost certainly not what you intended — something like q[1][0]
would be undefined behavior right away, since q[1]
is a null pointer and you'd be dereferencing it by doing [0]
on it.