`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
如您所见,p
和 r
包含相同的地址,那么
- 为什么
p[0]
有效而 r[0]
无效?
- 后面是什么
p[0]
?
- 为什么
(*r)[0]
有效而其他人却无效?
编辑:除了接受的答案外,这个答案也很有帮助
*p
是指向数组的指针,而(*r)[3]
是指向数组的数组指针。
*p
将指向数组 arr 的 values/indices,而 (*r)[i]
将指向索引的内存 location/address。
*r
将指向任何地方,因为 (*r)
和 *r
是不同的。
r
和 p
之间的区别仅在于类型。
首先要注意的是赋值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
的元素
p
和r
的类型大不相同:
p
是指向int
的指针,初始化指向数组arr
、 的第一个元素
r
是一个指向3的数组的指针int
:初始化不正确,应该是r = &arr
.
printf("%u %u", p, r)
具有未定义的行为:%u
期望类型为 unsigned int
的参数,p
和 r
是指针,应转换为 (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 *)
并打印使用 %p
。 r[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])
.
p
和r
(初始化为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
#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
如您所见,p
和 r
包含相同的地址,那么
- 为什么
p[0]
有效而r[0]
无效? - 后面是什么
p[0]
? - 为什么
(*r)[0]
有效而其他人却无效?
编辑:除了接受的答案外,这个答案也很有帮助
*p
是指向数组的指针,而(*r)[3]
是指向数组的数组指针。
*p
将指向数组 arr 的 values/indices,而 (*r)[i]
将指向索引的内存 location/address。
*r
将指向任何地方,因为 (*r)
和 *r
是不同的。
r
和 p
之间的区别仅在于类型。
首先要注意的是赋值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
的元素
p
和r
的类型大不相同:
p
是指向int
的指针,初始化指向数组arr
、 的第一个元素
r
是一个指向3的数组的指针int
:初始化不正确,应该是r = &arr
.printf("%u %u", p, r)
具有未定义的行为:%u
期望类型为unsigned int
的参数,p
和r
是指针,应转换为(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 *)
并打印使用%p
。r[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])
.
p
和r
(初始化为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