有一个二维数组,我想把它传给一个函数。于是我把函数接口定义出来了,如下:
- int array[2][3] = {1,2,3,4,5,6};
- void fun(int **array) {
- array[0][0] = 5;
- }
当我试图直接把数组名传给函数时候,fun(array)
编译会报错,大概意思就是类型不匹配。既然类型不匹配,那我就直接强转成你所需要的类型,于是我又做了调整,fun((int **)array)
,这下确实不报错了。但是此时我还没意识到问题的严重性。不出意外的情况下意外还是发生了,只要进入到这个函数后,程序就挂了。那你知道是什么原因吗?如果不清楚就往下看吧...
先从指针说起,指针是一个特殊的变量, 它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:指针的类型
、 指针所指向的类型
、 指针的值或者叫指针所指向的内存区
、 指针本身所占据的内存区
。
只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。例如:
- int*ptr; //指针的类型是 int*
- char*ptr; //指针的类型是 char*
- int**ptr; //指针的类型是 int**
- int(*ptr)[3]; //指针的类型是 int(*)[3]
只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉, 剩下的就是指针所指向的类型。例如:
- int*ptr; //指针所指向的类型是 int
- char*ptr; //指针所指向的的类型是 char
- int**ptr; //指针所指向的的类型是 int*
- int(*ptr)[3]; //指针所指向的的类型是 int()[3]
在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。
意思是指针本身占了多大的内存,在32位平台里,指针本身占据了4个字节的长度。可以使用sizeof(指针的类型)测试。
对于一个一维数组int array[10]
,数组名代表一个常量地址
,该地址指向第一个元素。以下两种情况数组名不能当指针使用。
对数组名取址,int *p_array = &array
,&这个运算符也很有讲究的,暂时不多说了。
sizeof(array)
计算的是整个数组在内存中所占用的空间。
二维数组本质上是以数组作为数组元素的数组,即“数组的数组”。假设我们定义了一个二维数组int array[2][3] = {1,2,3,4,5,6}
。
网上有很多地方都再说数组名array
和array[0]
、&array[0]
以及&array[0][0]
是等效的。那我们代码测试一下。
- printf("%#x,%#x,%#x,%#x\r\n",array,array[0],&array[0],&array[0][0]);
-
- Terminal:
- 0x404008,0x404008,0x404008,0x404008
因为这几种写法输出地址都是相同的,所以有的同学自然就认为这几种写法就是一样的。虽然地址相同,但是实际意义是有区别的,我们继续看下面的代码。
- //这里重新定义了指针变量,能够方便的知道右值得类型
- int *p_array1 = array[0];
- int *p_array2 = &array[0][0];
- int (*p_array3)[3] = &array[0];
- int (*p_array4)[3] = array;
-
- printf("%#X,%#X,%#X,%#X,%#X\r\n",array,++p_array1, ++p_array2, ++p_array3, ++p_array4);
-
- Terminal:
- 0X404008,0X40400C,0X40400C,0X404014,0X404014
根据以上实验分析能够看出:array[0]
与&array[0][0]
指针类型相同,都是int *
,地址存放的是int数据,当指针自增1时地址都偏移了一个int类型的大小。
&array[0]
与array
指针类型相同,都是int (*)[3]
,首先它是一个数组指针,这个指针指向一个数组,数组中数据的类型为int型。当指针自增1时地址都偏移了一个数组的长度(即3个int数据的大小)。
所以说array
只和&array[0]
真正意义等效。那怎么去理解这几种表达呢 ?
表示 | 含义 |
---|---|
array | 是一个数组指针 ,类型为int (*)[3] 。指向二维数组中第一个元素(元素是一维数组),指针所指向的内存大小为一维数组的长度 |
array[0] | 是一个指针,类型为int * 。就相当于一个一维数组名,指向一维数组中第一个元素的地址,指针所指向的内存大小为一个数据长度 |
&array[0] | 是一个数组指针 ,类型为int (*)[3] 。相当于对一维数组取地址。指针所指向的内存大小为一维数组的长度 |
&array[0][0] | 是一个指针,类型为int * ,是对二维数组中第一个数据取地址,注意是数据不是元素,指针所指向的内存大小为一个数据长度 |
如以上能够理解清楚,那么文中的问题应该就能够自己分析清楚了。
先定义一个二级指针int **p
,首先p是一个指针,在这个地址中存放的数据是指向一个整形数据的地址。
接着看文章中的问题,把一个二维数组强转成二级指针传给了函数。注意二维数组名的类型是一个数组指针
和二级指针
完全不是一个东西。那么会出现什么问题呢?
- int array[2][3] = {1,2,3,4,5,6};
-
- int main(int argc ,char **argv) {
- int **p_data = (int **)array;
- printf("%#x, %d\r\n", p_data, *p_data);
- }
-
- Terminal:
- 0x404008, 1
地址 | 数据 |
---|---|
0x404008 | 1 |
0x40400C | 2 |
0x404010 | 3 |
0x404014 | 4 |
0x404018 | 5 |
0x40401C | 6 |
看上面的例子,array的地址为0x404008,当把一个二维数组强转成二级指针的时候。p_data地址中存放的数据为1,因为二维数据中第一个数据就是1。根据二级指针的定义,这个数据1又会当成一个地址,该地址指向的内存才是最终的数据。
但是呢,这个地址1其实是个数据,并不是真正的地址。如果访问地址1中的数据,就属于非法访问地址了,可能会进入异常。
通过以上学习我们已经知道二维数组名就是一个数组指针
,我们函数就可以像下面这样声明。
- void fun(int array[][3], int row);
- void fun(int (*p_array)[3], int row);
- void fun(int row, int column, int array[row][column]);
最后在看下,应该如何定义与实参相对应的形参的数据类型。
含义 | 实参 | 形参 |
---|---|---|
二维数组(数组的数组) | int array[4][6] | int (*array)[6] |
指针数组(数组中的数据是指针) | int *array[6] | int **array |
数组指针(指向数组的指针) | int (*array)[6] | int (*array)[6] |
二级指针(指针的指针) | char **array | char **array |