Nebukadnezar
Nebukadnezar

Reputation: 77

Pointer to pointer assigned to a two-dimensional array, points to wrong address

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

Answers (3)

John Bode
John Bode

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

barak manos
barak manos

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

Dietrich Epp
Dietrich Epp

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

Related Questions