请求解析两段C语言程序输出差异的成因
拆解两段C代码的输出差异:数组退化、栈布局与未定义行为
咱们一步一步来捋清楚这个问题——这其实是C语言里数组退化为指针、栈内存布局结合起来导致的典型案例,还涉及到容易踩坑的未定义行为,难怪你会困惑!
先搞懂两个核心概念
int *p[4]是指针数组:你可以把它看成一个装了4个“地址标签”的盒子,每个标签都指向一个int类型的变量,而不是直接存整数。- 函数参数的数组会退化为指针:
poin(int p[4])看起来是接收一个int数组,但在C语言里,这种写法会自动变成poin(int *p)——函数实际拿到的只是一个指向int的指针,根本不知道原数组的长度。
第一段代码的输出原理
先看第一段代码的关键步骤:
- 在
main里,你给指针数组p的元素赋值:p[0]=&a(把变量a的地址放进第一个盒子),p[1]=&b、p[2]=&c、p[3]=&d。 - 调用
poin(*p)时,*p等价于p[0],也就是变量a的地址,你把这个地址传给了poin函数。 poin里的p是指向int的指针,指向a的地址:p[0]就是*(&a),也就是a的值1,这是第一个输出。- 后面三个输出要聊栈内存布局:C语言函数的栈是从高地址往低地址生长的,你声明变量的顺序是
int *p[4], a=1, b=2, c=3, d=4;,所以栈上先分配指针数组p(4个指针,共16字节),再依次分配a、b、c、d(每个4字节)。也就是说,a的地址后面紧接着的就是指针数组p的起始地址(a的地址+4字节就是p数组第一个元素的位置)。 - 所以
p[1]是从a的地址跳4字节后取的值,这个位置存的是p[0]里的&b(b的地址),用%u打印地址的十进制值就是2686716;同理p[2]取的是&c的地址,p[3]取的是&d的地址,对应后面两个输出。
第二段代码的输出原理
第二段代码只改了第14、15行:p[3]=&a,p[0]=&d——也就是把d的地址放进了指针数组的第一个盒子。
- 调用
poin(*p)时,传递的是变量d的地址。 - 再看栈布局:d是最后声明的变量,栈向下生长的特性让它的地址是四个int变量里最低的,后面紧接着就是c、b、a的地址(每个地址差4字节,正好是int的大小)。
- 所以
poin里的p指向d的地址:p[0]是d的值4,p[1]是d地址+4字节处的c的值3,p[2]是b的值2,p[3]是a的值1——正好对应你看到的输出。
为什么会有差异?核心是未定义行为!
两段代码的输出差异本质是栈内存的布局不同导致越界访问的结果不同,但这里必须强调:
这种越界访问是C语言里的未定义行为!
- 第一段代码里,
poin访问p[1]、p[2]、p[3]时,已经超出了变量a的内存范围,摸到了指针数组p的内存——换个编译器、编译选项甚至运行环境,输出可能完全不一样。 - 第二段代码的“正常输出”只是巧合:你的编译器恰好把d、c、b、a连续分配在了栈上,
poin的越界访问刚好覆盖了这四个变量,但这绝对不能当成可依赖的逻辑来写。
内容的提问来源于stack exchange,提问作者Hash
相关产品推荐
相关产品推荐

