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).