二维数组传参的本质是传递一个指向其首元素的指针。此处的“首元素”在二维数组的语境下具有特殊含义它不是一个基本数据类型如int或char而是构成该二维数组的一维数组。因此传递给函数的实际上是一个指向一维数组的指针即数组指针。为了深入理解这一本质需要从内存布局、指针类型、以及C语言函数传参机制三个维度进行剖析。二维数组在逻辑上呈现为行与列的矩阵但在物理内存中所有元素包括所有行中的所有列是连续线性存储的。例如一个定义为int arr[3][5]的数组其在内存中的排列顺序为arr[0][0],arr[0][1], ...,arr[0][4],arr[1][0], ...,arr[2][4]。当数组名arr作为参数传递时它遵循C语言中“数组名在大多数表达式中会退化为指向其首元素地址的指针”这一规则 。对于二维数组其首元素是第一个一维数组即arr[0]因此arr退化的结果是得到一个指向int [5]一个包含5个整型元素的一维数组类型的指针。这一本质决定了函数形参的两种等价写法。博客中明确指出二维数组传参形参部分可以写成数组形式也可以写成指针形式 。以下是一个具体的代码示例清晰地展示了这种等价性#include stdio.h // 写法一形参为数组指针 int (*p)[5] void print_array_pointer(int (*p)[5], int rows) { for (int i 0; i rows; i) { for (int j 0; j 5; j) { printf(%d , p[i][j]); // 等价于 *(*(p i) j) } printf( ); } } // 写法二形参为二维数组 int p[][5] 或 int p[3][5] void print_array_syntax(int p[][5], int rows) { for (int i 0; i rows; i) { for (int j 0; j 5; j) { printf(%d , p[i][j]); } printf( ); } } int main() { int arr[3][5] { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15} }; printf(通过数组指针调用: ); print_array_pointer(arr, 3); // arr 退化为指向 int[5] 的指针 printf( 通过二维数组语法调用: ); print_array_syntax(arr, 3); // 编译器内部仍将其视为 int (*)[5] return 0; }在两种写法中编译器最终处理的方式是相同的print_array_syntax函数中的int p[][5]在编译时会被调整为int (*p)[5]。省略第一维的大小rows是允许且常见的因为该信息已通过独立的参数传递或硬编码在列数中。但第二维列数的大小必须明确指定这是理解该本质的关键。因为指针的算术运算依赖于其指向类型的大小。一个int (*p)[5]类型的指针执行p1操作时会跳过一整行5个int的字节数从而指向下一行的起始地址。若第二维大小未知编译器无法计算出正确的偏移量。这种设计在实际编程中直接影响了多维数组的动态传递与处理。例如在处理图像像素矩阵、数学矩阵运算或任何表格化数据时必须遵循此规则。下表对比了不同维度的数组传参时发生的“退化”现象数组维度数组定义示例传参时退化成的指针类型说明一维数组int arr[10]int*指向单个元素的指针。二维数组int arr[3][5]int (*)[5]指向一维数组的指针数组指针必须指定列数。三维数组int arr[2][3][4]int (*)[3][4]指向二维数组的指针必须指定后两维的大小。因此当函数需要接收一个行数可变但列数固定的二维数组时必须使用数组指针作为形参。这种机制也解释了为何不能直接将一个动态分配的、行指针数组如int**传递给期望int (*)[N]类型的函数因为两者的内存布局和指针寻址方式截然不同。前者是一个指针数组每个元素指向独立分配的行后者则指向一块连续的、按行优先顺序排列的内存区域。理解这一区别对于避免内存访问错误和编写高效、正确的C语言程序至关重要。参考来源【DAY13】深⼊理解指针(4)