Search code examples
arrayscpointers

Need help understanding pointer to a type of n elements in C (int (*p)[10])?


I am studying pointers in C and have written the following code:

int main() {
    int a = 10;
    int *pa = &a;
    printf("Value of pa : %p\n", pa);         // 0x7fffd215b5b8
    printf("Value of *pa : %d\n", *pa);       // 10

    int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int (*parr)[10] = &arr;
    
    printf("Address of arr : %p\n", &arr);    // 0x7fffd215b5d0
    printf("Address of parr : %p\n", &parr);  // 0x7fffd215b5c0
    printf("Value of parr : %p\n", parr);     // 0x7fffd215b5d0
    printf("Value of *parr : %p\n", (*parr)); // 0x7fffd215b5d0 <---WHY?
    printf("Value of first element : %d\n", **parr); //1

    // In order to print elements using pointer parr
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
        printf("%d\t", *((*parr)+i));  // 1 2 3 ... values stored in arr

    return 0;
}

For the pointer pa, the value that it holds is the address of the variable a. When we do *pa we are de-referencing the (value of p)/(address of a) i.e *(0x7fffd215b5b8) which is equal to 10.

For pointer parr, the value that it holds is the base address of an array of 10 elements (0x7fffd215b5d0). Shouldn't *parr i.e. *(0x7fffd215b5d0) de-reference the value that it contains similar to what *pa did previously and output 1? Why does *parr display the value that it contains i.e. base address of arr?

And why do I need to de-reference the pointer twice to get the values for the array (I have managed to get the output by trial and error and don't really understand why it's supposed to work this way). The pointer *parr that I have initialized is a single pointer and not a double pointer right?


Solution

  • An array decays into a pointer to its first element.


    First, the basics.

    arr is an array.
    It has type int [10].
    The array is located at 0x7fffd215b5d0.

    parr is a pointer to an array.
    It has type int (*)[10].
    The pointer is located at 0x7fffd215b5c0.
    Its value is 0x7fffd215b5d0.

    *parr is an array.
    It has type int [10].
    The array is located at 0x7fffd215b5d0.

    Now on to the question.

    Except in a couple of very specific situations (&a, sizeof a, typeof a), using an array doesn't produce the array but a pointer to its first element. We say it decays into a pointer to its first element.

    arr decays into a pointer to its first element.
    It has type int *.
    Its value is 0x7fffd215b5d0.

    *parr decays into a pointer to its first element.
    It has type int *.
    Its value is 0x7fffd215b5d0.

    The code in question is equivalent to the following:

    printf("Value of *parr : %p\n", &((*parr)[0]));
    

    Using pointers to arrays complicates things. It's more common to use pointers to the first element of arrays.

    int *pele = arr;  // Same as `&(arr[0])`
    printf("Value of first element : %d\n", *pele);    // 1
    printf("Value of first element : %d\n", pele[0]);  // 1
    

    More complete demo:

    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    int (*parr)[10] = &arr;
    int *pele = arr;  // Same as `&(arr[0])`
    
    printf( "Address of arr:           %p\n", (void*)&arr  );
    printf( "Address of parr:          %p\n", (void*)&parr );
    printf( "Value of parr:            %p\n", (void*)parr  );
    printf( "Address of pele:          %p\n", (void*)&pele );
    printf( "Value of pele:            %p\n", (void*)pele  );
    printf( "\n" );
    printf( "Address of first element:\n" );
    printf( "via decay of arr:         %p\n", (void*)arr   );
    printf( "via decay of *parr:       %p\n", (void*)*parr );
    printf( "via pele:                 %p\n", (void*)pele  );
    printf( "\n" );
    printf( "Value of first element:\n" );
    printf( "*p, via decay of arr:     %d\n", *arr         );
    printf( "*p, via decay of *parr:   %d\n", **parr       );
    printf( "*p, via pele:             %d\n", *pele        );
    printf( "p[0], via decay of arr:   %d\n", arr[0]       );
    printf( "p[0], via decay of *parr: %d\n", (*parr)[0]   );
    printf( "p[0], via pele:           %d\n", pele[0]      );
    

    In response to comment, a little about dynamically allocating 2d arrays.

    A common convention to allocate an array dynamically is to use

    Type *p = malloc( n * sizeof *p );
    

    It's no exception with 2d arrays.

    // Non-dynamic.
    int a[rows][cols];                                                a[row][col]
    
    // Same, but saves decayed pointer.
    int a[rows][cols];  int (*p)[cols] = a;                           p[row][col]
    
    // Dynamic.
                        int (*p)[cols] = malloc( rows * sizeof *p );  p[row][col]
    

    p is a int (*)[cols], so
    *p is a int [cols], so
    sizeof *p is the size of a int [cols], so
    sizeof *p is equal to cols * sizeof int, so
    rows * sizeof *p is equivalent to rows * cols * sizeof int.