本文使用运行环境如下:
操作系统:Ubuntu Linux 18.04 64 bit
编译环境:gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
0. 引言
数组指针
和指针数组
且听之类似,实际完全不同。数组指针意在“指针”,强调的是指针,即“指向数组的指针”;指针数组意在“数组”,强调的是数组,即“成员都是指针的数组”。
本文尝试阐述二者的不同之处。
1. 数组指针
数组指针如何定义与使用?先看一个示例程序arr_p.c
#include <stdio.h>
int main()
{
/* 定义一个有10个int成员的数组 */
int arr_i[10] = {0,1,2,3,4,5,6,7,8,9};
/* 定义一个指向有10个int成员的数组指针,并初始化为arr_i数组的地址 */
int (*arri_p)[10] = &arr_i;
/* 定义一个2*2的二维数组,在内存中以一维排列,实际是数组的数组
arr数组成员类型为int (*)[2],数组元素为arr[0]和arr[1]
对arr的数组成员arr[0]和arr[1],其本身也是数组,成员为int
*/
int arr[2][2] = {
{1,2},
{3,4}
};
/* 定义一个指向有2个int成员的数组指针,并初始化为arr数组第0个成员的地址 */
int (*arr_p)[2] = &arr[0];/* 等价于int (*arr_p)[2] = arr; */
printf("数组arr_i的地址: %#lx\n", (unsigned long int)&arr_i);
printf("数组指针arri_p的值: %#lx\n", (unsigned long int)arri_p);
printf("数组指针arri_p+1的值: %#lx\n", (unsigned long int)(arri_p+1));
printf("==========================================\n");
printf("数组arr的地址: %#lx\n", (unsigned long int)&arr);
printf("数组指针arr_p的值: %#lx\n", (unsigned long int)arr_p);
printf("数组指针arr_p+1的值: %#lx\n", (unsigned long int)(arr_p+1));
printf("arr第一个成员的地址&arr[1]: %#lx\n", (unsigned long int)&arr[1]);
printf("数组指针arr_p+1处的数组值: %d\n", *(int*)(arr_p+1));
return 0;
}
执行结果:
$ gcc arr_p.c
$ ./a.out
数组arr_i的地址: 0x7ffc5289ace0
数组指针arri_p的值: 0x7ffc5289ace0
数组指针arri_p+1的值: 0x7ffc5289ad08
==========================================
数组arr的地址: 0x7ffc5289acd0
数组指针arr_p的值: 0x7ffc5289acd0
数组指针arr_p+1的值: 0x7ffc5289acd8
arr第一个成员的地址&arr[1]: 0x7ffc5289acd8
数组指针arr_p+1处的数组值: 3
arr_i
是一个有10个int成员的数组,数组类型为int [10]
,arri_p
是这个数组类型的指针并指向了arr_i
数组。在代码第20行和21行的打印中,arri_p
指向了arr_i
数组,因此数组arr_i的地址
和指针arri_p的值
必然相等;
22行对指针arri_p
进行指针运算,指针指向的类型是int [10]
,这里是64位系统64位程序,对指针+1运算就转化成了(unsigned long int)arri_p+sizeof(arr_i)*1
,arr_i
数组占用内存大小为40个字节,因此指针arri_p
+1之后将偏移40个字节(数组的大小),刚好0x7ffc5289ad08
- 0x7ffc5289ace0
= 0x28
= 40
;
这里数组指针arri_p+1
之后其指向了数组范围之外,这样一看指针运算似乎就没什么用了,还有内存越界的风险。
再看13行往下的代码,定义了一个类型为int (*)[2]
的二维数组arr
,同样用一个数组指针arr_p
指向arr
数组,28行对指针arr_p
+1时,其结果也是和arr
数组偏移一个成员之后的地址相同。这里可以看出指针运算的原理是通用的,对指针+1将偏移指针指向对象的类型大小。
那么指针arr_p
+1后偏移了多少字节?0x7ffc5289acd8
- 0x7ffc5289acd0
= 0x8
= 8
,即一个int [2]
成员的大小,就是arr
数组第1行{1,2}
所占用的空间,似乎不够有说服力,30行对arr_p
+1后的位置进行取值,结果表明arr_p
+1后指向了二维数组arr
第2行第1个元素值为3,即arr_p
+1后在二维数组中“换了一行”,因此有的地方也将数组指针称为“行指针”。
2. 指针数组
顾名思义,指针数组是一个成员为“同一指针类型的数组”,这里也通过一个示例程序进行说明。p_arr.c
#include <stdio.h>
int main()
{
int i = 0;
int arr[3] = {1,2,3};
int a = 10;
/* int *p[4]等价于int *(p[4]) */
int *p[4] = {&arr[0], &arr[1], &arr[2], &a};
for(i = 0; i < sizeof(p)/sizeof(*p); i++)
{
printf("*p[%d] = %d\n", i, *p[i]);
}
return 0;
}
运行结果:
$ gcc p_arr.c
$ ./a.out
*p[0] = 1
*p[1] = 2
*p[2] = 3
*p[3] = 10
指针数组较之数组指针易于理解,p
是一个有4个成员的数组,每个成员都是int *
类型,由于每个成员都是int *
,因此p[i]
既表示p数组的第i个成员,又表示某个int
对象的地址。要访问p数组中保存的对象的地址所指向的对象,就需要对p数组中的成员解引用
(取相应地址保存的数据),12行中*p[i]
是对p数组中的第i个成员解引用
,这种方式能够正常访问合法的地址对象。
3. 小结
- 数组指针本质是指针,是指向数组的指针;
- 指针数组本质是数组,是保存指针的数组;
从对指针数组和数组指针的分析来看,虽然两者都和指针相关,但指针和数组不可同日而语,对二者的混淆很大可能是中文概念的混淆。