Let's talk about expressions and types.
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
" is converted ("decays") to an expression of type "pointer to T
", and the value of the expression is the address of the first element of the array.
The expression arr
has type "2-element array of 3-element array of double
". In the line
printf( "%ld \n", (long) arr);
arr
is not the operand of the &
or sizeof
operators, so it is converted to an expression of type "pointer to 3-element array of double
", and its value is the address of the first element, or &arr[0]
.
In the line
printf( "%ld \n", (long) *arr);
since the expression arr
has type "pointer to 3-element array of double
", the expression *arr
(which is equivalent to the expression arr[0]
) has type "3-element array of double
". Since this expression isn't the operand of the sizeof
or unary &
operators, it is converted to an expression of type "pointer to double
", and its value is the address of the first element, or &arr[0][0]
.
In C, the address of the array is the same as the address of the first element of the array (no separate storage is set aside for a pointer to the first element; it is computed from the array expression itself). The array is laid out in memory as
+---+
arr: | 1 | 0x0x7fffe59a6ae0
+---+
| 2 | 0x0x7fffe59a6ae8
+---+
| 3 | 0x0x7fffe59a6aec
+---+
| 4 | 0x0x7fffe59a6af0
+---+
| 5 | 0x0x7fffe59a6af8
+---+
| 6 | 0x0x7fffe59a6afc
+---+
So the following expressions will all yield the same value, but the types will be different:
Expression Type Decays to
---------- ---- ---------
arr double [2][3] double (*)[3]
&arr double (*)[2][3] n/a
*arr double [3] double *
arr[i] double [3] double *
&arr[i] double (*)[3] n/a
*arr[i]
and arr[i][j]
both yield a double
value.
So now let's look at ptrptr
:
double **ptrptr = (double **) arr;
printf( "%ld \n", (long) ptrptr);
printf( "%ld \n", (long) *ptrptr);
The first thing that we notice is that ptrptr
is not an array expression, so the conversion rule above doesn't apply. We assign it to point to the first element of arr
, but after that it behaves like any other pointer, so the expressions ptrptr
and *ptrptr
will have different values. Since ptrptr
points to the first element of the array (&arr[0][0]
), *ptrptr
yields the value stored at the first element, which is 1.00
. Just so happens that when you interpret the bit pattern for 1.00
as a long integer on this particular platform, it comes out as 4607182418800017408.
Here's some code that may make the above a little more clear:
#include <stdio.h>
int main( void )
{
double arr[][3] = {{1,2,3},{4,5,6}};
double **ptrptr = (double **) arr;
printf( " arr: %p\n", (void *) arr );
printf( " &arr: %p\n", (void *) &arr );
printf( " *arr: %p\n", (void *) *arr );
printf( " arr[0]: %p\n", (void *) arr[0] );
printf( "&arr[0]: %p\n", (void *) &arr[0] );
printf( " ptrptr: %p\n", (void *) ptrptr );
printf( "*ptrptr: %p (%f %ld)\n", (void *) *ptrptr,
*(double *) ptrptr, *(long int *) ptrptr );
return 0;
}
And here's the output:
arr: 0x7fffe59a6ae0
&arr: 0x7fffe59a6ae0
*arr: 0x7fffe59a6ae0
arr[0]: 0x7fffe59a6ae0
&arr[0]: 0x7fffe59a6ae0
ptrptr: 0x7fffe59a6ae0
*ptrptr: 0x3ff0000000000000 (1.000000 4607182418800017408)
Again, *ptrptr
gives us the value at the first element of the array, which is 1.0
, but we're interpreting that bit pattern as a pointer value (0x3ff0000000000000
) and a long integer (4607182418800017408
).