Home > Enterprise >  Difference between `int *p` and `int (*p)[3]`?
Difference between `int *p` and `int (*p)[3]`?

Time:02-22

#include <stdio.h>

int main() {
    int arr[3] = { 1, 2, 3 };
    int *p = arr;
    int (*r)[3] = arr;
    
    printf("%u %u", p, r);
    printf("\n%d %d %d", p[0], p[1], p[2]);
    
    printf("\n%d %d %d", r[0], r[1], r[2]);
    printf("\n%d %d %d", *r[0], *r[1], *r[2]);
    printf("\n%d %d %d", (*r)[0], (*r)[1], (*r)[2]);
}

Output:

1483745184 1483745184
1 2 3
1483745184 1483745196 1483745208
1 0 -647513344
1 2 3

As you can see p and r contains same address, then

  • why does p[0] work but r[0] doesn't?
  • what goes behind p[0]?
  • why does (*r)[0] work but others don't?

CodePudding user response:

The difference between these two declarations

int *p = arr;
int (*r)[3] = arr;

is that in the second declaration there is used a wrong initializer. The array arr used as an initializer is implicitly converted to pointer to its first element of the type int *. So in the second declaration the initialized object and the initializer have different pointer types and there is no implicit conversion between the types.

To make the second declaration correct you need to write

int (*r)[3] = &arr;

Now the both pointers p and r stores the same value: the address of the extent of memory occupied by the array but have different types.

For example if you will write

printf( "sizeof( *p ) = %zu\n", sizeof( *p ) );
printf( "sizeof( *r ) = %zu\n", sizeof( *r ) );

then the first call will output the size of an object of the type int that is equal to 4 while the second call will output the size of the whole array of the type int[3] that is equal to 12.

In this your call of printf

printf("%u %u", p, r);

there are used incorrect conversion specifiers with pointers. Instead you have to write

printf("%p %p", ( void * )p, ( void * )r);

The expressions r[0], r[1], r[2] have the type int[3]. Used in this call

printf("\n%d %d %d", r[0], r[1], r[2]);

they as it was mentioned are implicitly converted to pointers to their first elements. But these arrays except the array r[0] that denotes the array arr do not exist. So there takes place an access to memory beyond the array arr,

You could write

printf( "\n%p\n", ( void * )r[0] );

and this call will be equivalent to

printf("\n%p\n", ( void * )arr );

This call of printf

printf("\n%d %d %d", *r[0], *r[1], *r[2]);

is also incorrect because arrays r[1] and r[2] of the type int[3] do not exist.

This call of printf

printf("\n%d %d %d", (*r)[0], (*r)[1], (*r)[2]);

outputs elements of the array arr due to using the expression *r.

CodePudding user response:

The difference between r and p is solely in the type.

The first thing to note is that the assignment int (*r)[3] = arr; is not correct; arr in assignments decays to a pointer to its first element, an int, which is a different type than r which is a pointer to an array of three ints. Now admittedly, C originally wasn't that picky, and assignments between different pointer types and between pointers and integers weren't given much thought — it's all numbers, right? But modern C tries to be more type safe, for good reason, so the proper assignment would be int (*r)[3] = &arr;: If you want the address of an array, just take the address. Your code "works" because numerically the address of the first element is the address of the array. It's the type that's wrong, not the value.

Now to your confusion: As you noted, both point to the same address; but the object p is pointing to is a simple int (which just so happens to be followed by two more ints in memory, together comprising arr), while r is pointing to the array itself.

Consequently, the type of *p is int while the type of *r is int[3], and consequently to that sizeof *r == 3 * sizeof *p holds.

As you know, C blurs that distinction in most contexts: For example, you could legitimately say p = *r;, because arrays are "adjusted" or "decay" to pointers to their first element in assignments or parameter initialization.

But for sizeof they don't. That is because indexing adds index * sizeof(element) to the numerical value of the pointer; if the pointer points to an entire array, like r (as opposed to its first element only, like p), the second element will be the next array, which isn't there — there is only one array, so your program is faulty.

#include <stdio.h>
int main() {
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*r)[3] = arr;

    printf("%p %p\n", (void*)r[0], (void*)r[1]);
}

This little program illustrates this. Note how arr again decays to the address of its first element; only this time, the first element of that two-dimensional array is an array itself, of three ints, so it fits perfectly.

CodePudding user response:

The types of p and r are very different:

  • p is a pointer to int, initialized to point to the first element of array arr,
  • r is a pointer to an array of 3 int: the initialization is incorrect, it should be r = &arr.
  • printf("%u %u", p, r) has undefined behavior: %u expects an argument with type unsigned int, p and r are pointers which should be cast as (void *) and convered with %p.
  • printf("\n%d %d %d", p[0], p[1], p[2]) is correct and produces 1 2 3 as expected
  • printf("\n%d %d %d", r[0], r[1], r[2]) has undefined behavior for multiple reasons: r[0] and r[1] decay as pointers to int, they should be cast as (void *) and printed using %p. r[2] is an invalid pointer: computing its value and passing it as an argument has undefined behavior.
  • postfix unary operators bind stronger than prefix operators, so *r[0] as parsed as *(r[0]), which is the same as r[0][0], the first element of the array arr. Conversely *r[1] is equivalent to r[1][0], which refers to an invalid area, beyond the end of arr, same for *r[2], so reading both of these cause undefined behavior.
  • conversely (*r)[0] is the same as (r[0])[0], hence r[0][0], the first element of arr, and similary (*r)[1] is the same as r[0][1] so printf("\n%d %d %d", (*r)[0], (*r)[1], (*r)[2]) outputs 1 2 3 just like printf("\n%d %d %d", p[0], p[1], p[2]).

p and r (initialized as r = &arr) indeed point to the same location, but they have different types which must be taken into consideration when writing code.

Here is a modified version:

#include <stdio.h>

int main() {
    int arr[3] = { 1, 2, 3 };
    int *p = arr;
    int (*r)[3] = &arr;

    printf("arr: address %p,  sizeof(arr): %2zu bytes,  sizeof(*arr): %2zu bytes\n",
           (void *)arr, sizeof(arr), sizeof(*arr));
    printf("p:   address %p,  sizeof(p):   %2zu bytes,  sizeof(*p):   %2zu bytes\n",
           (void *)p, sizeof(p), sizeof(*p));
    printf("r:   address %p,  sizeof(r):   %2zu bytes,  sizeof(*r):   %2zu bytes\n",
           (void *)r, sizeof(r), sizeof(*r));

    printf("arr: %p,  arr 1: %p\n", (void *)arr, (void *)(arr   1));
    printf("p:   %p,  p 1:   %p\n", (void *)p, (void *)(p   1));
    printf("r:   %p,  r 1:   %p\n", (void *)r, (void *)(r   1));

    printf("%d %d %d\n", p[0], p[1], p[2]);
    printf("%d %d %d\n", r[0][0], r[0][1], r[0][2]);
    printf("%d %d %d\n", (*r)[0], (*r)[1], (*r)[2]);

    return 0;
}

Output:

arr: address 0x7fff544137f8,  sizeof(arr): 12 bytes,  sizeof(*arr):  4 bytes
p:   address 0x7fff544137f8,  sizeof(p):    8 bytes,  sizeof(*p):    4 bytes
r:   address 0x7fff544137f8,  sizeof(r):    8 bytes,  sizeof(*r):   12 bytes
arr: 0x7fff544137f8,  arr 1: 0x7fff544137fc
p:   0x7fff544137f8,  p 1:   0x7fff544137fc
r:   0x7fff544137f8,  r 1:   0x7fff54413804
1 2 3
1 2 3
1 2 3

CodePudding user response:

*p is a pointer to an array, while (*r)[3] is an array pointer to array.

*p will point to the values/indices of the array arr. (*r)[i] will point to the memory location/address of the indices.

*r will point no where as (*r) and *r are DIFFERENT.

  • Related