一、先记住一句话
C 语言里没有真正的"二维",只有"一维的线性内存"。
所谓二维数组,只是把数据 一行一行地 铺在连续的内存上,这就是"行优先"存储。
二、画个图就懂了
比如 int a[2][3] = { {1,2,3}, {4,5,6} };
逻辑上我们这样看:
a[0][0] a[0][1] a[0][2]
a[1][0] a[1][1] a[1][2]
但内存里实际是线性排布:
低地址 ──────────────────── 高地址
| 1 | 2 | 3 | 4 | 5 | 6 |
↑第 0 行 ↑第 1 行
关键点:第 1 行的开头紧跟在第 0 行的结尾之后,整个数组是一块连续空间。
三、代码验证,地址不会骗人
c
#include <stdio.h>
int main() {
int a[2][3] = {{1,2,3}, {4,5,6}};
printf("%p\n", &a[0][0]); // 第0行第0个
printf("%p\n", &a[0][1]); // 第0行第1个
printf("%p\n", &a[0][2]); // 第0行第2个
printf("%p\n", &a[1][0]); // 第1行第0个
printf("%p\n", &a[1][1]); // 第1行第1个
printf("%p\n", &a[1][2]); // 第1行第2个
return 0;
}
可能的输出(每个 int 占 4 字节):
0x7ffc8e3a2a10
0x7ffc8e3a2a14
0x7ffc8e3a2a18
0x7ffc8e3a2a1c ← 紧接在 a[0][2] 之后,完美连续!
0x7ffc8e3a2a20
0x7ffc8e3a2a24
地址一个接一个,每次 +4,行与行之间没有空隙,彻底实锤行优先连续存储。
四、再品一下指针,理解更透
a[1][2] 为什么能工作?其实就是一维地址的换算:
a是数组名,代表整个二维数组的首地址(类型是int (*)[3],指向含3个int的一维数组)a[1]就是第1行这个一维数组的首地址a[1][2]等价于*(a[1] + 2),再深层等价于*(*(a + 1) + 2)
底层计算:
a[1][2] 的真实地址 = 首地址 + (1*3 + 2) * sizeof(int)
刚好在内存偏移的第5个int位置,和前面看到的连续布局完全一致。
五、总结(记住这三点足够)
- 二维数组在内存中按行连续存放,行内连续,行间也连续。
- 不管多少维,本质都是线性空间,编译器只是帮我们把"aij"自动转换成了一维偏移。
- 理解这点,对指针传参、动态分配、
memset/memcpy操作数组都会豁然开朗。
如果你觉得有帮助,欢迎点赞、收藏、评论,让更多人看到!