C语言中二维数组和二维数组分析

问题

最近有个同事发现一个问题:一个二维数组,想把它传给一个函数,具体代码如下:

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 **arrayint** 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的未知空间,因此发生段错误。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值