`int *p` 和 `int (*p)[3]` 之间的区别?

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

#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]);
}

输出:

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

如您所见,pr 包含相同的地址,那么

编辑:除了接受的答案外,这个答案也很有帮助

*p是指向数组的指针,而(*r)[3]是指向数组的数组指针。

*p 将指向数组 arr 的 values/indices,而 (*r)[i] 将指向索引的内存 location/address。

*r 将指向任何地方,因为 (*r)*r 是不同的。

rp 之间的区别仅在于类型。

首先要注意的是赋值int (*r)[3] = arr;不正确;赋值中的 arr 衰减为指向其第一个元素的指针,即 int,它与 r 不同,后者是指向三个整数数组的指针。现在承认,C 最初并没有那么挑剔,不同指针类型之间以及指针和整数之间的赋值并没有考虑太多——都是数字,对吧?但是现代 C 有充分的理由试图提高类型安全性,因此正确的赋值应该是 int (*r)[3] = &arr;:如果您想要数组的地址,只需获取地址即可。您的代码“有效”,因为数字上第一个元素的地址数组的地址。错误的是类型,不是值。

现在让您感到困惑的是:正如您所指出的,两者都指向同一个地址;但是 p 指向的对象是一个简单的 int(正好在内存中后面跟着两个 int,一起构成 arr),而 r 指向数组本身。

因此,*p 的类型是 int,而 *r 的类型是 int[3],因此 sizeof *r == 3 * sizeof *p 成立。

如您所知,C 在大多数情况下模糊了这种区别:例如,您可以合理地说 p = *r;,因为数组被“调整”或“衰减”为指向其赋值或参数中第一个元素的指针初始化。

但是 sizeof 他们没有。那是因为索引将index * sizeof(element)加到指针的数值上;如果指针指向整个数组,如 r(与它的第一个元素相反,如 p),第二个元素将是 下一个数组 ,它不存在——只有一个数组,所以你的程序有问题。

#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]);
}

这个小程序说明了这一点。请注意 arr 如何再次衰减到其第一个元素的地址;只是这一次,two-dimensional 数组的第一个元素本身就是一个数组,包含三个整数,因此非常适合。

这两个声明的区别

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

是在第二个声明中使用了错误的初始值设定项。用作初始值设定项的数组 arr 被隐式转换为指向其类型 int * 的第一个元素的指针。所以在第二个声明中,初始化对象和初始化器具有不同的指针类型,并且类型之间没有隐式转换。

要使第二个声明正确,您需要编写

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

现在指针p和r都存储了相同的值:数组占用的内存范围的地址,但类型不同。

例如,如果你会写

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

那么第一次调用将输出 int 类型的对象的大小等于 4 而第二次调用将输出 [=] 类型的整个数组的大小24=] 等于 12.

在此调用 printf

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

使用了不正确的指针转换说明符。相反,你必须写

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

表达式 r[0]、r[1]、r[2] 的类型为 int[3]。在本次通话中使用

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

如前所述,它们被隐式转换为指向其第一个元素的指针。但是除了表示数组arr的数组r[0]之外,这些数组都不存在。所以发生了对数组 arr,

之外的内存的访问

你可以写

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

此调用等同于

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

本次调用printf

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

也不正确,因为 int[3] 类型的数组 r[1]r[2] 不存在。

本次调用printf

printf("\n%d %d %d", (*r)[0], (*r)[1], (*r)[2]);
由于使用表达式 *r.

输出数组 arr 的元素

pr的类型大不相同:

  • p是指向int的指针,初始化指向数组arr
  • 的第一个元素
  • r是一个指向3的数组的指针int:初始化不正确,应该是r = &arr.
  • printf("%u %u", p, r) 具有未定义的行为:%u 期望类型为 unsigned int 的参数,pr 是指针,应转换为 (void *) 并与 %p.
  • 交谈
  • printf("\n%d %d %d", p[0], p[1], p[2]) 是正确的并按预期生成 1 2 3
  • printf("\n%d %d %d", r[0], r[1], r[2]) 由于多种原因具有未定义的行为:r[0]r[1] 衰减为指向 int 的指针,它们应该被转换为 (void *) 并打印使用 %pr[2] 是无效指针:计算其值并将其作为参数传递具有未定义的行为。
  • 后缀一元运算符比前缀运算符绑定更强,所以 *r[0] 解析为 *(r[0]),与 r[0][0] 相同,数组的第一个元素 arr.相反 *r[1] 等同于 r[1][0],它指的是无效区域,超出 arr 的末尾,与 *r[2] 相同,因此读取这两个都会导致未定义的行为。
  • 相反,(*r)[0](r[0])[0] 相同,因此 r[0][0]arr 的第一个元素,并且相似 (*r)[1]r[0][1] 所以 printf("\n%d %d %d", (*r)[0], (*r)[1], (*r)[2]) 输出 1 2 3 就像 printf("\n%d %d %d", p[0], p[1], p[2]).

pr(初始化为r = &arr)确实指向同一个位置,但它们的类型不同,编写代码时必须考虑。

这是修改后的版本:

#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;
}

输出:

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