C语言二维数组在内存中是如何存储的?

一、先记住一句话

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位置,和前面看到的连续布局完全一致。


五、总结(记住这三点足够)

  1. 二维数组在内存中按行连续存放,行内连续,行间也连续。
  2. 不管多少维,本质都是线性空间,编译器只是帮我们把"aij"自动转换成了一维偏移。
  3. 理解这点,对指针传参、动态分配、memset/memcpy 操作数组都会豁然开朗。

如果你觉得有帮助,欢迎点赞、收藏、评论,让更多人看到!

相关推荐
码行山野赴时序归途11 小时前
从暴力到最优:三道 C 语言入门题的解法思路
c语言·开发语言·数据结构·算法·leetcode·排序算法
sunoo-22912 小时前
【Linux 系统编程】学习第七天:线程属性 | 互斥锁 | 死锁 | 信号量 核心知识点 + 踩坑实战
linux·c语言·笔记·vscode·学习
小雪崩12 小时前
嵌入式学习 day33:线程进阶
linux·c语言·学习
SuperByteMaster13 小时前
ARM Architecture Procedure Call Standard(ARM架构过程调用标准)
c语言
神仙别闹17 小时前
基于C语言+SQLite 实现(控制台+图形界面)图书管理系统
c语言·开发语言·sqlite
水饺编程17 小时前
第5章,[Win32 章节] :绘制填充区域
c语言·c++·windows·visual studio
从零开始的嵌入式之旅18 小时前
day31
linux·c语言·经验分享·笔记·嵌入式硬件
Horn Still Sounds18 小时前
Linux系统编程进阶:进程回收、程序替换与多线程并发模型
linux·运维·服务器·c语言
xu_wenming1 天前
嵌入式软件架构中的6种解耦艺术
c语言·驱动开发·嵌入式硬件·架构
DevHub1 天前
CANopen 教程:STM32 实现 CANopen 从站,3 个回调替代手写对象字典
c语言·驱动开发·stm32·单片机·嵌入式硬件·mcu