问题
最近有个同事发现一个问题:一个二维数组,想把它传给一个函数,具体代码如下:
char array[3][128];
void fun(char** array)
{
strcpy(array[0],"confirm");
}
当我试图直接把二维数组名传给函数的时候,fun(array)编译会报错,类型不匹配。于是我就直接强转成所需要的类型fun((char**)array),这样就不会报错了,但是编译运行后,发现程序还是挂了。
指针
指针是一个特殊的变量,他里面存储的数值被解释为内存里的一个地址。要搞清楚一个指针,需要搞清楚指针的四方面的内容:
1.指针的类型
只要把指针声明语句里的指针名字去掉,也就是把指针变量取消,剩下的类型就是指针类型。
例如:
intptr; //指针类型为int
charptr; //指针类型为char
int ** ptr; //指针类型为int**
int (ptr)[3]; //指针类型为int()[3];
2.指针所指向的类型
只要把指针变量和其中的*去掉,剩下的就是指针指向的类型
int ptr; //指针所指向的类型是int
char ptr; //指针所指向的类型是char
int**ptr; //指针所指向的类型为int*
int (*ptr); //指针所指向的类型是int()[3]
3.指针的值
在C语言中,只要是变量,那么他就有实际的值。指针也一样,既然他是一个变量(指针变量),那么他也有值,他的值可以用printf(“%p”,ptr);打印出来。
在32位平台中,所有类型的指针的值是一个32位的整数。指针本身也是需要占用内存的,在32位平台,指针本身占据了4个字节的长度。可以使用sizeof(ptr)测试。
一维数组
对于一个一维数组int array[10]。数组名代表一个常量地址,该指针指向第一个元素,一维数组还是比较好理解的。
二维数组
二维数组本质是是以数组作为数组元素的数组。即“数组的数组”。假设我们定义了一个二维数组:
int array[2][3] = {{1,2,3},{4,5,6}};
经过测试分别打印:
array,array[0],&array[0],以及&array[0][0]等的地址都是相同的。
虽然这几种写法输出的地址是相同的额,但是实际意义是有区别的。
int * p1_array = array[0];
int * p2_array = &array[0][0];
int (*p3_array)[3] = &array[0];
int (*p4_array)[3] = array;
printf("array=%p\n",array);//打印:0X404008
printf("p1_array=%p\n",++p1_array);//输出:0X40400C
printf("p2_array=%p\n",++p2_array);//输出:0X40400C
printf("p3_array=%p\n",++p3_array);//输出:0X404014
printf("p4_array=%p\n",++p4_array);//输出:0X404014
依据以上实验分析:array[0],与&array[0][0]指针类型相同,都是int*,地址存放的都是整型数据,当指针自增1时,地址都便宜一个int类型的大小。
&arra[0]与array指针类型相同,都是int(*)[3]。他是一个数组指针,这个指针指向一个数组,数组中的数据类型是整型。他指针自增1时,地址都是偏移了一个数组长度3个int数据的大小)。
二维数组的各个表达式的含义:
array :是一个数组指针,类型为int()[3]。指向二维数组中的第一个元素(元素是一位数组)。指针每+1偏移的内存大小为一维数组的长度。
array[0],是一个int的指针,指向一维数组的第一个元素的地址,指针每+1偏移的内存大小为int的长度。
&array[0],与array是一致的。
&array[0][0],与array[0]是一致的。
二级指针
先定义一个二级指针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("%p, %p\r\n", p_data, *p_data);
}
打印输出为:
0x3bec28, 0x1
看上面的例子,array的地址为0x3bec28,当把一个数组强转成二级指针的时候,p_data地址中存放的数据为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** arry |
数组指针 | int (*array)[6] | int(* arry)[6] |
二级指针 | int **array | int** array |
最后的分析
二维数组内存分配
二维数组是”数组的数组“,因而我们很容易产生二维数组名是一个二级指针的错觉,实际上并不是。实际上,不管是一维还是多维数组,都是内存中一块线性连续的空间,因此在内存级别上,其实都只是一维的,但是不同的定义使得表现形式不一样,从而有多维数组的概念。
访问数组元素其实非常简单,原因就在于元素在内存中的线性排列。这样对一维数组的访问:
arr1[index] = (arr1+indexsizeof(Type));
对二维数组的访问:
arr2[i][j]=(arr2+(icol+j)*sizeof(Type));
结论
为什么不能将二维数组名强制转换成二级指针
int main()
{
int data[2][3] = {{3,8,4},{4,5,6}};
int** ptr = (int **)data;
printf("%p,%p\n",ptr,data);
printf("%p\n",ptr[0]);
return 0;
}
ptr和data本质上都是指针,在赋值后,他们指向了同一片内存空间
64位机器中,int占4个字节,int *占8个字节,因此ptr[0]就是data[0][0]和data[0][1]拼接起来的结果,故ptr[0]的值为0x800000003。可以看到,这个值并非data数组首元素的地址。因此当进行如下调用fun(ptr,2,3)时,试图访问的是0x800000003的未知空间,因此发生段错误。