Reputation: 77
I'm new to C and although I thought I pretty much got the whole logic concerning pointers and arrays I've come across an issue that doesn't make any sense to me.
Consider a 2D array, say
double arr[][3] = { {1,2,3}, {4,5,6} };
and a pointer to a pointer
double ** ptrptr;
Now, let's say printing the address that arr
and arr[0]
point to, respectively, i.e.
printf( "%ld \n", (long) arr);
printf( "%ld \n", (long) *arr);
yields something like
140734902565640
140734902565640
since the first element of arr
(array of arrays) and the first element of *arr
(array of doubles) have the same location. So far so good. Now to my problem:
I do this:
ptrptr = (double **) arr;
printf( "%ld \n", (long) ptrptr);
printf( "%ld \n", (long) *ptrptr);
and I would expect the same output as before, but instead I get something like
140734902565640
4607182418800017408
So it seems that ptrptr
and arr
both evaluate to a pointer to the same location - as expected - but *ptrptr
and *arr
do not. Why?
Also, if I dereference once more, i.e. **ptrptr
, I get a segmentation fault.
My logic went sth. like this, loosely speaking:
ptrptr == arr == &arr[0]
should point to
*ptrptr == *arr == arr[0] == &arr[0][0]
which in turn should point to
**ptrptr == *arr[0] == arr[0][0]
.
Although I found a workaround that will serve my purpose, I would still really like to understand why this doesn't work.
Upvotes: 3
Views: 629
Reputation: 123568
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
).
Upvotes: 3
Reputation: 30146
An array can be seen as a "label":
Unlike a pointer, it doesn't have an lvalue
, and you cannot set it to a different value after declaring it.
Try arr = ptrptr
or arr = (double**)0x80000000
, and you'll get a compilation error.
That is why, for any type of array arr
, the following is always true: arr == &arr
.
In addition, please note the memory-map differences:
double arr[3][3]; // A consecutive piece of 9 `double` units.
double** ptrptr // 1 pointer unit.
ptrptr = new double*[3]; // A consecutive piece of 3 pointer units.
for (int i=0; i<3; i++)
ptrptr[i] = new double[3]; // 3 separate pieces of 3 consecutive `double` units.
So not only does ptrptr
consume an additional amount of 4 pointer units in comparison with arr
, but it also has the 9 double
units allocated in 3 separate sections in memory.
Hence arr = &arr = &arr[0] = &arr[0][0]
, but ptrptr != *ptrptr != **ptrptr
.
Upvotes: 1
Reputation: 213688
The difference is most clearly illustrated with a diagram.
This is a normal 2D array in C, double[4][3]
.
+---+---+---+ | | | | +---+---+---+ | | | | +---+---+---+ | | | | +---+---+---+ | | | | +---+---+---+
This is a double pointer in C, double **
. It is pointing to the head of a double *[4]
, each of which points to the head of a double[3]
.
+----------+ +---+ +---+---+---+ | +---->| +------>| | | | +----------+ +---+ +---+---+---+ | +-----+ +---+ | +---+---+---+ | +---+ +>| | | | +---+ | +---+---+---+ | +-+ | +---+ | | +---+---+---+ | +-->| | | | | +---+---+---+ | | +---+---+---+ +---->| | | | +---+---+---+
Notice that the syntax for accessing elements is the same.
double arr[4][3];
arr[1][2] = 6.28;
double **ptr = ...;
ptr[1][2] = 6.28;
However, what is actually happening is quite different. So you cannot simply cast one type to the other.
With the double[4][3]
, you find an element by doing a little bit of math to calculate the offset.
With the double **
, you find an element by following a chain of pointers.
Upvotes: 2