You need to enable JavaScript to run this app. 为何PUSH指令的寄存器列表允许LR却不包含PC?会产生不可预测行为的原因是什么?
优惠活动
大模型
产品
解决方案
定价
更多

为何PUSH指令的寄存器列表允许LR却不包含PC?会产生不可预测行为的原因是什么?

为何PUSH {R0, PC}会产生不可预测行为

不可预测行为的根源

首先需要明确:ARM体系里的PUSH并不是独立的指令集操作,它本质是满递减栈场景下多寄存器存储指令STMDB SP!, <寄存器列表>的助记符,核心动作是读取列表内所有寄存器的值,按顺序写入SP指向的栈内存,最后调整SP指针位置。
把PC加入PUSH的寄存器列表会触发不可预测行为,本质是PC的硬件设计属性从根上就不适合作为多寄存器存储的源操作数:

  • PC不是存储通用数据的寄存器文件单元,而是和CPU流水线深度强绑定的动态值。不同ARM核心的流水线深度不同,PC的读值偏移没有统一的兼容规则:比如传统ARM状态下PC为当前指令地址+8,传统Thumb状态下为当前指令地址+4,随着核心迭代流水线从3级升级到更高级别,这个偏移没有固定标准。
  • 架构规范从设计之初就没有定义“执行PUSH时,应该抓取哪个阶段的PC值写入栈内存”。是取指阶段的PC?译码阶段的?还是执行阶段的?不同硬件实现可以返回任意值,不存在可依赖的确定执行结果,因此直接将该组合标记为UNPREDICTABLE——也就是软件绝对不能假定这个指令的执行结果是固定的。
  • 从硬件实现成本看,广泛用于嵌入式的经典ARM7TDMI核心,其多寄存器存储的读取通路根本没有连接PC的总线,强行支持该指令会拖慢关键时序路径,且没有实际收益。

为何允许PUSH LR,却不允许PUSH PC

两者的核心差异是硬件属性的不同:

  • LR(R14)是和R0~R12完全等价的通用寄存器,不存在动态值属性。它的特殊点仅仅是执行BL跳转指令时,硬件会自动把“跳转目标执行完需要返回的指令地址”写入LR,但寄存器本身的值是完全固定的:只要软件或硬件向LR写入了值,不管在流水线哪个阶段读取,拿到的都是写入的固定值,和用单寄存器STR指令存储LR的效果完全一致,没有任何不确定性。把LR加入PUSH列表没有任何技术障碍,是完全合法的操作,也是ARM上实现函数调用标准栈帧的基础。
  • PC的读、写语义是完全不对称的:
    • 写PC的操作(比如常用的POP {PC})是架构明确定义的:从内存读值写入PC时,硬件统一执行流水线冲刷、上下文同步,跳转到对应地址,不管核心流水线深度如何,这个操作的行为是完全确定的,因此是合法的常用指令。
    • 读PC的操作没有统一的语义标准,读出来的值根本无法确定,自然不允许作为存储类指令的源寄存器使用。

关于递归实现的认知误区

你提到“借助PC编写递归”其实是对寄存器语义的混淆:
实现递归函数需要保存的核心是当前函数的返回地址,这个需求从ARM架构设计之初就是靠LR专门适配的:函数入口执行PUSH {LR}把返回地址压栈,递归调用时新的LR会被BL指令自动覆盖为子函数的返回地址,函数退出时执行POP {PC}把之前压栈的返回地址弹到PC,就能完成正确的函数返回。
这套流程完全不需要主动读取PC的值——如果硬要读PC来计算返回地址,不仅会因为流水线偏移拿到错误的地址,还会因为PC读值的不确定性,写出无法跨硬件移植的代码,完全没有实际价值。

内容的提问来源于stack exchange,提问作者Vighnesh Nayak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.30 02:54:03