C语言指针自增运算解析:第6、7条printf的输出异常与执行逻辑
逐步拆解第6、7条printf的自增执行逻辑
先明确执行到第6条之前的固定状态
顺着代码的执行轨迹捋到第5条printf结束的节点,所有变量状态是完全确定的,不存在天生的随机值:
- 基础初始化:静态数组
a仅包含5个int元素,合法下标范围是0~4;静态指针数组p的初始值为{&a[0], &a[1], &a[2], &a[3], &a[4]}。 - 经过前面5次自增/解引用操作后,最终停留的状态:
- 二级指针
ptr固定指向p[4](也就是ptr = &p[4],后续所有操作都不会改动ptr本身的指向,这也是第6、7条printf第一个输出值恒为4的核心原因) p[3]被之前的++*ptr操作修改过,从初始的&a[3]变成了&a[4];p[4]在此之前从未被修改,初始值为&a[4]。
- 二级指针
核心拆解:第6条printf前的*++*ptr执行逻辑
按照C语言运算符规则(同优先级右结合、前置++优先级高于解引用*),整句表达式的执行顺序拆成三步:
- 先计算最靠近操作数的
*ptr:取出ptr指向的p[4]里存储的值,也就是初始的&a[4] - 对这个解引用结果做前置++:把
p[4]里存的&a[4]加1,更新为&a[5]注意这里直接越界了,a数组没有下标为5的元素,这个指针指向a数组末尾相邻的静态内存区域 - 对更新后的
p[4]的值再做一次解引用:读&a[5]位置的int值,没有其他修改操作
这行执行完,ptr的指向完全没动,p[4]的值被改成了a数组外的地址。此时第6条printf输出的三个值就完全可解释了:
ptr - p:ptr一直指着p[4],和p数组首地址的差值永远是4*ptr - a:*ptr就是p[4]现在存的&a[5],和a首地址的差值是5**ptr:读越界地址&a[5]的内容,这个位置不属于a数组的合法范围,存储的是编译器安排在静态区的其他内部数据,没有用户定义的固定值,所以看起来像随机整数(你运行时看到的4204752就是这个位置的实际存储值)
核心拆解:第7条printf前的++**ptr执行逻辑
同样按优先级右结合的规则拆成三步:
- 先算
*ptr:还是取p[4]里存的&a[5],没有任何变化 - 对这个结果再解引用,得到
**ptr:也就是刚才第6条读到的那个越界地址的存储值4204752 - 对这个解引用结果做前置++:把越界地址里存的int值加1,从4204752变成4204753
这行执行完,ptr的指向没动,p[4]存的指针值也没动,只是修改了那个越界地址里存的整数。此时第7条printf的输出也就完全对应上了:
- 前两个值还是4、5(因为ptr的位置、p[4]存的指针都没改)
- 第三个值是被加了1的越界地址存储值,也就是4204753
为什么会出现“看似随机”的数值
本质是代码触发了数组越界访问:因为a数组只有5个合法元素,经过*++*ptr的操作后,p[4]从指向a[4](合法范围)变成指向a[5](完全越界),后续对这个越界指针的读写都属于未定义行为。因为操作的是静态存储区的内存,不会触发段错误,只是读到/写入的是编译器、运行时自行安排的内存值,不同编译环境、不同编译参数下这个值都不一样,就给人“随机数”的错觉。
内容的提问来源于stack exchange,提问作者saurabh singh
相关产品推荐
相关产品推荐

