C语言二维数组传参时int**与变长数组形参结果差异原因
核心原因:两种形参的指针类型完全不同,寻址逻辑天差地别
首先要明确一个基础规则:二维数组int ar[3][2]本质是数组的数组,它的每个元素是int[2]类型的一维数组。当数组名作为函数参数传递时,会退化为指向首个元素的指针,也就是int (*)[2]类型(指向长度为2的int数组的指针),和int**类型完全不兼容。
1. 变长数组形参的运行逻辑
当你声明形参为int ar[rows][cols]时,C语法会自动把这种数组类型的形参调整为指向其首元素的指针,也就是等价于int (*ar)[cols],刚好和你传入的二维数组退化后的指针类型完全匹配。
此时ar[rows - 1][cols - 1]的寻址逻辑是:
- 以
ar存储的基地址为起点,偏移(rows-1) * sizeof(int[cols])字节,得到第rows-1行的首地址 - 再以该行首地址为起点,偏移
(cols-1) * sizeof(int)字节,读取目标元素的值
整个过程是直接对基地址做两次偏移计算,完全适配二维数组连续内存的布局,所以能正确拿到结果。
2. int**形参的运行逻辑
int**是指向int指针的指针,它的寻址逻辑和数组指针完全不同:
- 第一步执行
ar[rows-1]时,会先把ar存储的地址作为起点,偏移(rows-1) * sizeof(int*)字节,读取该地址的内容,把读出来的数值当成一个int*类型的行指针 - 第二步再以这个读出来的行指针为起点,偏移
(cols-1)*sizeof(int)字节读取目标元素
你传入的是int (*)[2]类型的数组指针,相当于把普通指针强转成了指向指针的指针给函数。函数执行第一步寻址时,会把二维数组里存储的实际数值(比如你示例里的第一个元素1)当成内存地址去访问,直接触发非法内存访问的段错误,程序自然终止。
关于汇编寻址一致的疑问
你大概率是看错了汇编的操作属性,或者是开启高等级优化时编译器对未定义行为做了非常规处理。正常无优化场景下,两者的汇编逻辑完全不同:
- 变长数组版本的两次地址计算后,只有第二次是读取数组元素值
int**版本第一次计算后就读取了数组值作为地址,还没走到后续计算就已经触发段错误
如果确实看到汇编计算出了正确地址,也是C标准允许的:未定义行为的执行结果没有任何保障,编译器生成任何逻辑的汇编都符合规则。
如果你一定要用int**作为形参接收二维数组,需要先构造指针数组存储每一行的首地址,再传入函数:
int main() { int ar[3][2] = {{1, 2}, {3, 4}, {5, 6}}; int *p_ar[3] = {ar[0], ar[1], ar[2]}; foo(3, 2, p_ar); }
这种情况下int**版本的函数也能正常运行。
内容的提问来源于stack exchange,提问作者milanHrabos
相关产品推荐
相关产品推荐

