一码惊醒梦中人
曾经的我天真的以为C语言中的二维数组都是“指针数组套娃”(因为可以两次取值),但是上个学期的C语言课无情的打破了我的幻想——二维数组也是线性存储的(不过字符串数组确实是指针数组套娃)。
但是我真的搞明白了么?其实直到几个小时前,我依旧认为二维数组名和二级指针没什么差别,直到我尝试运行了一下下面的代码
#include<stdio.h>
int main()
{
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
int **q;
q = a;
printf("%d\n", **q);
}
libxzr@libxzr-virtual-machine:~$ gcc test.c
test.c: In function ‘main’:
test.c:11:4: warning: assignment to ‘int **’ from incompatible pointer type ‘int (*)[4]’ [-Wincompatible-pointer-types]
11 | q = a;
| ^
编译时会出现警告,不过没什么关系,一个强制类型转换的事
运行一下
libxzr@libxzr-virtual-machine:~$ ./a.out
段错误 (核心已转储)
草?
??????
为什么对二维数组名两次取值没有事,但是把它赋给一个二级指针就炸了?
我真的学过C语言吗?
是时候好好梳理一下,二维数组名、行指针、二级指针之间的关系了。
尘埃落定
我也在网上搜索了相关的资料,才知道某年菊厂面试题里也有这玩意儿(草)
网上的各种解释有很多,但是我觉得还是纯粹的从变量类型上去解释最为合适,因为变量类型作为C语言中的“天理”是不可撼动的
它是怎么发生的?
要想知道为什么会这样,其实只需要知晓下面的几个点就足够了
- 二维数组是怎么存储的?
- 二维数组的数组名是个什么?
- 二维数组的数组名变量里面装了什么?
- 二维数组的数组名在一次取值之后是个什么?
- 二维数组的数组名在一次取值之后,变量里面装了什么?
- 上面的二级指针里面,又装了什么?
上面的点其实都挺明显,而这个问题的答案就藏在其中
先来回答上面的问题
- 线性存储
- 行指针 (
类型 (*名称)[第一维度的变量个数]
) - 二维数组开始的地址
- 普通的指针
- 二维数组第一行的开始地址
而二维数组开始的地址
==二维数组第一行的开始地址
,这就是问题的所在
用代码来表示,就是
#include<stdio.h>
int main()
{
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
printf("%d\n", a==*a);
}
libxzr@libxzr-virtual-machine:~$ gcc test.c
test.c: In function ‘main’:
test.c:13:18: warning: comparison of distinct pointer types lacks a cast
13 | printf("%d\n", a==*a);
| ^~
libxzr@libxzr-virtual-machine:~$ ./a.out
1
二维数组名和其取值的变量类型不同,但是,存储的地址相同,而这个地址,是
- 二维数组开始的地址
- 二维数组第一行开始的地址
- 二维数组第一行第一个数的地址
所以如果将一个二维数组名赋值给二级指针,等同于将二维数组第一行第一个数的地址赋值给一个二级指针
而这个二级指针一次取值就得到了二维数组第一行第一个数,再取值就直接进入无权限访问的内存区域了。。。
梳理
我更愿意将这个问题的原因归结于变量类型,因为在这里扯什么地址毫无意义
上述的几个地址都是相同的
对二维数组名进行一次取值实际上是进行了一次“虚空操作”,这次操作没有改变指向的地址,仅仅只是改变了变量的类型
所以梳理变量类型才是关键
首先,二维数组名是个什么,我在上面已经提到,它是一个行指针,那么什么是行指针?
我们已经知道了行指针的一个特点,那就是对它进行一次取值,只产生了一个不同类型新变量,但是它指向的地址没有发生改变
那么,行指针和那个对它进行取值产生的东西有什么区别?
区别就在于,行指针含有一个额外的信息,那就是第一维度的变量数(二维数组中可以理解为列数)
只有掌握了这个信息,我们才能对行指针进行如a[1][2]
的二维取值,否则,它怎么知道要从头指针开始向后推多少个内存地址才能找到目标?也正是因为有了这个信息,我们才能通过对行指针加减来整行移动指向位置。这个信息存在哪?变量类型里
总结一下:
- 二维数组名是一个行指针
- 对行指针取值产生普通指针
- 行指针和这个普通指针变量的内容是一样的(指向相同地址)
- 行指针与普通指针的变量类型不同,行指针的变量类型直接含有了第一维度变量数的信息
实战一下?
#include<stdio.h>
int main()
{
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
int *q;
q = a+1;
printf("%d\n", *q);
}
只要明白了各变量分别存储着什么,就可以随意整活
而不会被拘禁于一维数组是一级指针,二维数组是二级指针的奇怪想法
libxzr@libxzr-virtual-machine:~$ gcc test.c
test.c: In function ‘main’:
test.c:9:5: warning: assignment to ‘int *’ from incompatible pointer type ‘int (*)[4]’ [-Wincompatible-pointer-types]
9 | q = a+1;
| ^
libxzr@libxzr-virtual-machine:~$ ./a.out
4