想象一间米其林三星后厨:主厨站在灶台前,四位得力助手各守一方天地。RAX是主厨的调味台——所有菜出锅前,最后的调味都在这里定稿;RBX是仓库管理员手里的定位牌——一翻牌子,就知道食材在货架第几排;RCX是帮厨腕上的节拍器——翻炒几次、炖煮几分钟,全凭它默默计数;RDX则是传菜口旁的备料板——暂时安放等待下锅的食材。这就是x86处理器中最核心的四个通用寄存器。它们从8086时代的16位“小推车”、80386时代的32位“中推车”,一路升级到x86-64的64位“大货架”。变的是容量,不变的是这四份分工默契。学会它们,你就拿到了读懂汇编代码的第一把钥匙。

岗位关联

通用寄存器是指令集架构(ISA)的基石。对AI从业者而言,无论是编译器后端的寄存器分配、操作系统上下文切换,还是性能分析与逆向调试,都绕不开这四位“CPU帮厨”的分工逻辑。

目录
  1. 可视化演示
  2. 寄存器演进史——从16位小推车到64位大货架
  3. RAX累加器——主厨的调味台
  4. RBX基址寄存器——仓库货架定位牌
  5. RCX计数寄存器——后厨的节拍器
  6. RDX数据寄存器——传菜口的备料板
  7. 子寄存器兼容——操作台的分区抽屉
  8. 调用约定规则——后厨与前厅的交接规矩
  9. 工程实践应用——完整后厨的运作流程
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示x86寄存器位扩展、RAX/RBX/RCX/RDX用途及调用约定的三模态交互动画。

本节配有交互动效(进入视口后自动加载,约 3KB)。

寄存器位宽扩展历程 64位 32位 RAX⊃EAX⊃AX x86寄存器从8位AL/AH逐步扩展到16位AX再到32位EAX最终到64位RAX保持向后兼容

寄存器演进史——从16位小推车到64位大货架

后厨的备料推车从16英寸换到32英寸,再换到64英寸大货架,每一位老帮厨都记得自己最初的分工。x86寄存器体系的演进也是如此:1978年,英特尔8086处理器诞生,自带8个16位通用寄存器,这是后厨最早的“小推车”;1985年,80386处理器把寄存器扩展到32位,推车换成了中型货架;2003年,AMD推出x86-64架构,寄存器终于升级到完整的64位。

每一次升级都不是推倒重来,而是“旧城改造”——新的大货架保留着旧推车的所有尺寸和功能。这就是x86最核心的向后兼容原则:一个40年前编写的16位程序,放到今天的64位处理器上,只要它只使用旧寄存器,依然能跑。这种兼容性的秘密藏在寄存器的嵌套关系里。

从16位到64位,这四位老帮厨始终没忘当年的手艺。新的大货架里,底层永远留着最初的小推车——这种“旧城改造”式的设计哲学,让x86成为计算机历史上寿命最长的指令集架构。

RAX(64)⊃EAX(32)⊃AX(16)⊃{AH(8),AL(8)}\displaystyle \text{RAX}_{(64)} \supset \text{EAX}_{(32)} \supset \text{AX}_{(16)} \supset \{ \text{AH}_{(8)}, \text{AL}_{(8)} \}
这个大货架的底部区域就是32位的EAX,再缩小是16位的AX,AX还能拆成高8位AH和低8位AL,像俄罗斯套娃一样层层嵌套。
  1. RAX(64) 全尺寸这是64位寄存器的完整形态,好比后厨大货架的整层台面,主厨的调味料全部铺开在这里。
    RAX(64)\text{RAX}_{(64)}
  2. EAX(32) 中号抽屉RAX的低32位就是EAX,是32位时代的主力寄存器,新货架保留着它的专属抽屉。
    EAX(32)\text{EAX}_{(32)}
  3. AX(16) 小号抽屉EAX的低16位叫AX,这是8086时代的原始尺寸,老厨师们最顺手的那一格。
    AX(16)\text{AX}_{(16)}
  4. AH/AL(8) 最小收纳盒AX还能再拆成高8位AH和低8位AL,货架上最小号的收纳盒,却仍然贴着当年的标签。
    {AH(8),AL(8)}\{ \text{AH}_{(8)}, \text{AL}_{(8)} \}

RAX累加器——主厨的调味台

主厨的调味台是后厨的C位。一勺盐、一勺糖,所有调料在这个台面上调配,最终汤汁的味道就在这里定稿。RAX在CPU中的地位,正如这块调味台——它是累加器(Accumulator),是算术逻辑单元(ALU)所有运算结果的默认归宿。加法、减法、比较的结果,都会先放进RAX,就像调味料先在台面上过一遍才能出锅。

RAX还有另一重身份:函数返回值的专属通道。在C语言中写return x;,编译器会把x放进RAX再返回给调用者。主厨在调味台定稿的味道,就是程序里每个函数交出的答卷。

主厨的调味台虽然不大,但每道菜的味道定稿都经过它——RAX在程序中的分量,就像调味台在后厨的分量。所有函数调用的最终答案,都要在这一方台面上揭晓。

RAX←RAX+操作数\displaystyle \text{RAX} \leftarrow \text{RAX} + \text{操作数}
add指令执行后,旧RAX加上操作数的新结果又写回RAX。就像主厨在调味台上反复试味、加料、再试味——累加器的“累”字,正体现在这不断累积的过程中。
  1. RAX ← 操作方向箭头表示把右边计算结果回写到左边的寄存器,类似于“倒进调味台”。
    RAX←\text{RAX} \leftarrow
  2. RAX + 操作数 求和过程CPU的ALU把RAX当前值与操作数相加,调味台上新旧食材完成混合。
    RAX+操作数\text{RAX} + \text{操作数}
  3. 再回填 累积更新加完之后的新值留在RAX,下一次计算可以继续在此基础上累加。这就是“累加器”名字的由来。
    RAX=旧值+操作数\text{RAX} = \text{旧值} + \text{操作数}

RBX基址寄存器——仓库货架定位牌

后厨仓库有几百个货架格子,没有定位牌,找食材就像大海捞针。RBX就是那张定位牌——它全称基址寄存器(Base Register),专门存放一块内存区域的起始地址。CPU访问数组、结构体这样的大块数据时,先拿到RBX中的基址,再叠加一个偏移量(Offset)去定位具体元素。

这种“基址+偏移”的寻址模式,是现代CPU内存访问的骨架。比如遍历一个包含一万个学生的成绩数组,RBX指向数组首元素,每次只需要改变偏移量就能顺序访问下一个学生。没有它,每次访问都要计算完整地址,效率会低得多。

有了定位牌,再大的仓库也不怕找不着食材。同样,有了RBX,再长的数组也能从容从头遍历到尾。它就像一个安静的领航员,守护着CPU在内存迷宫中不迷路。

有效地址=[RBX]+偏移量\displaystyle \text{有效地址} = [\text{RBX}] + \text{偏移量}
方括号表示“取出RBX寄存器里存的那个数值”,也就是基址。基址加上偏移量,得到真正要访问的内存地址——就像定位牌告诉你“货架3排第5格”,偏移量就是从排头往右数几步。
  1. 解除引用 [RBX]方括号是x86汇编里的“解除引用”记号,取出RBX内部存储的地址值,而不是它本身的编号。
    [RBX][\text{RBX}]
  2. 偏移量 步数从基址往前走多少个字节的距离,相当于从货架起点数到目标商品的距离。
    偏移量\text{偏移量}
  3. 有效地址 最终位置基址加上偏移量,CPU得到真正的内存地址并把它送上地址总线去取数。
    有效地址\text{有效地址}

RCX计数寄存器——后厨的节拍器

帮厨手腕上的节拍器,是后厨节奏的灵魂。炖汤要“小火慢炖40分钟”,节拍器滴答走完,起锅关火。RCX就是CPU的节拍器——它的全称是计数寄存器(Counter),专门负责循环计数。x86汇编里的loop指令,会默认把RCX减一,判断减完是否为零;不为零就跳回循环开头,为零则继续往下执行。

除了循环控制,RCX还兼任移位指令的“位数计数器”。shl(左移)和shr(右移)指令需要一个移位位数,这个数字就存放在RCX里。好比节拍器能设定“翻炒8下”或“炖煮40分钟”,RCX告诉CPU“左移4位”还是“右移3位”。

帮厨数到"0"那一刻,起锅、关火、上菜。CPU的loop指令也一样,RCX数到0,循环终结,程序继续走向下一行代码。这个看似简单的递减动作,撑起了所有循环结构的底层实现。

LOOP:RCX←RCX−1;若 RCX≠0 则跳转 LOOP\displaystyle \text{LOOP}: \text{RCX} \leftarrow \text{RCX} - 1; \quad \text{若 } \text{RCX} \neq 0 \text{ 则跳转 LOOP}
每次循环末尾,RCX自动减一,不为零就跳回循环开头。这就是“计数器驱动循环”的机器级实现,和节拍器“啪嗒、啪嗒”地数拍子如出一辙。
  1. LOOP 循环入口标号这是循环起点的标记,告诉CPU往回跳的目标地点,相当于节拍器的“第1拍”。
    LOOP:\text{LOOP:}
  2. RCX 递减每次迭代结束RCX自动减一,节拍器走一格。
    RCX←RCX−1\text{RCX} \leftarrow \text{RCX} - 1
  3. 零判断 是否继续判断RCX是否变为零,不为零就重新跳回LOOP入口。
    RCX≠0\text{RCX} \neq 0

RDX数据寄存器——传菜口的备料板

传菜口旁有一块备料板。传菜口正忙的时候,掌勺师傅会把暂时不用的食材放在上面——既不占操作台,又随手能拿到。RDX在CPU中扮演的角色,就是这块备料板。它的全称是数据寄存器(Data Register),专门在算术运算中充当“扩展位”或“余数位”。

当CPU执行64位乘法mul时,两个64位数相乘可能得到128位结果——一个寄存器装不下。于是硬件规定:高64位暂存在RDX,低64位放在RAX,两个寄存器以RDX:RAX的形式拼成一个128位整体。除法运算中,RDX存放的是余数,比如13除以5,商是2,余数是3——RAX装着商,RDX就是那个被放在备料板上的余数"3"。

备料板虽不显眼,但掌勺师傅心里有数——需要扩展位、存余数时,第一个想起的就是它。RDX低调,却总在关键处补位,让CPU的大数运算稳稳落地。

RDX:RAX←RAX×操作数\displaystyle \text{RDX}:\text{RAX} \leftarrow \text{RAX} \times \text{操作数}
RDX:RAX表示一个由两个寄存器拼接而成的128位数——高64位在RDX,低64位在RAX。就像一整块大披萨,切成两半分别装进两个盒子。
  1. RDXRAX 拼接记号:冒号表示把RDX和RAX拼接成一个128位的寄存器对,高64位归属RDX,低64位归属RAX。
    RDX:RAX\text{RDX}:\text{RAX}
  2. RAX × 操作数 乘法操作被乘数预先放在RAX中,操作数来自另一个寄存器或立即数。
    RAX×操作数\text{RAX} \times \text{操作数}
  3. 回填两个寄存器乘积的高64位写回RDX、低64位写回RAX,各自回到自己的“盒子”里。
    RDX←高64位,RAX←低64位\text{RDX} \leftarrow \text{高64位}, \quad \text{RAX} \leftarrow \text{低64位}

子寄存器兼容——操作台的分区抽屉

超大号的厨师操作台,台面一半放砧板,一半放调料,底下还保留着好几个旧抽屉。老师傅用惯了老抽屉,所以无论台面怎么换,旧抽屉的尺寸必须原样保留。x86-64的子寄存器设计正是如此:RAX这个大台面里,还嵌着EAX、AX、AH、AL这些“旧抽屉”,尺寸一丝一毫都没变。

这种嵌套结构在64位模式下有一个极其重要的行为差异:写入32位寄存器(如EAX)会自动把RAX的高32位清零,而写入16位(AX)或8位(AL/AH)则不影响其他位。用后厨的话说:你把整个中层抽屉翻新(写EAX),台面上层的东西会被清空重摆;但你只是动了最底层的小格子(写AL),台面上的一切保持不动。

大台子再怎么扩大,老抽屉的尺寸不变——这是x86帝国延续数十年“非物质文化遗产”。读懂这个兼容机制,你就理解了为什么新旧程序能在同一块CPU上和平共处。

写入EAX⇒RAX=ZeroExtend(EAX)\displaystyle \text{写入EAX} \Rightarrow \text{RAX} = \text{ZeroExtend}(\text{EAX})
ZeroExtend是“零扩展”——把32位的EAX当作无符号数,在高32位补0,凑成完整的64位RAX。好比一个32厘米的旧抽屉放进64厘米的新柜子,空出来的32厘米必须用泡沫(0)填满。
  1. 写入EAX 触发条件程序向32位子寄存器EAX赋值,这个动作会触发特殊的兼容处理。
    写入EAX\text{写入EAX}
  2. ZeroExtend 零扩展零扩展(Zero Extend)将32位数值视为无符号,在其上方添加32个0。
    ZeroExtend(EAX)\text{ZeroExtend}(\text{EAX})
  3. RAX = 扩展结果新的RAX就是扩展后的64位值,旧的高32位被0覆盖清除。
    RAX=ZeroExtend(EAX)\text{RAX} = \text{ZeroExtend}(\text{EAX})

调用约定规则——后厨与前厅的交接规矩

后厨和前厅之间有一条看不见的“交接规矩”:哪道菜先出、从哪个窗口交菜、谁负责报菜名——这些规则不写进菜谱,但所有人严格遵守。调用约定(Calling Convention)就是程序世界中后厨与前厅的交接规矩。它明确规定:函数调用时参数放在哪些寄存器、返回值放在哪个寄存器、栈帧由谁来清理。

Linux/Unix世界最通用的是System V AMD64调用约定:前6个整数参数依次放入RDI、RSI、RDX、RCX、R8、R9,第7个及更多参数压入栈,返回值则始终放在RAX中。这条规矩让C语言编译出的代码可以调用汇编写的函数,也让Python的底层C扩展能和解释器无缝对接。

规矩定了,后厨和前厅才能高效配合。调用约定统一了,不同语言编译出的代码才能互相调用。这就像国际象棋的规则全球统一——无论你是AI模型写的代码还是程序员手写的汇编,都能在同一张棋盘上过招。

f(a,b,c,d)⇒RDI=a, RSI=b, RDX=c, RCX=d\displaystyle f(a,b,c,d) \Rightarrow \text{RDI}=a,\ \text{RSI}=b,\ \text{RDX}=c,\ \text{RCX}=d
调用函数f(a, b, c, d)时,四个实际参数按顺序坐进了由System V规范指定的四台“专车”——RDI、RSI、RDX、RCX,抵达函数体后与形参一一对应。
  1. f(a,b,c,d) 高级语言视角这是C/C++等语言中一次普通函数调用的样子,程序员不需要关心参数如何传递。
    f(a,b,c,d)f(a,b,c,d)
  2. RDI=a, RSI=b 前两个参数System V约定把第1个参数放进RDI,第2个参数放进RSI,这是前两趟“专车”。
    RDI=a, RSI=b\text{RDI}=a,\ \text{RSI}=b
  3. RDX=c, RCX=d 后两个参数第3、第4个参数分别乘坐RDX和RCX。注意:RDX在这里传参数,和它做乘法扩展位并不冲突,分工场景不同。
    RDX=c, RCX=d\text{RDX}=c,\ \text{RCX}=d

工程实践应用——完整后厨的运作流程

凌晨五点,后厨开始一天的运转:帮厨们各司其职,调味台、定位牌、节拍器、备料板全部就位;晚上打烊,所有厨具归位、台面擦净,为第二天备好状态。在真实工程中,寄存器也遵循着这样井井有条的运作节奏。

编译器后端有一项核心优化叫寄存器分配:把高级语言里的成百上千个局部变量,巧妙地映射到屈指可数的寄存器上。AI框架的推理引擎在优化矩阵乘法时,就会把RAX、RBX分配给循环中的小矩阵切片,让数据尽可能留在CPU内部、减少访问内存——这正是后厨把常用食材放在手边的道理。操作系统做上下文切换时,则要把RAX、RBX、RCX、RDX等所有寄存器值压入栈帧保存,等线程恢复运行再原样铺回——这就是“打烊归档、开门复岗”。

打烊后的厨房,所有工具归位;进程暂停时,所有寄存器值存档。这份井井有条,就是计算机系统稳定运行的底气——也是理解AI框架高性能推理背后“寄存器级优化”的第一块拼图。

上下文={RAX,RBX,RCX,RDX,RSI,RDI,RSP,RBP,…}\displaystyle \text{上下文} = \{ \text{RAX}, \text{RBX}, \text{RCX}, \text{RDX}, \text{RSI}, \text{RDI}, \text{RSP}, \text{RBP}, \ldots \}
进程从运行切换到暂停的瞬间,CPU把这套寄存器值打包存档;下个时间片轮到它时,再原封不动地恢复——这就是“上下文切换”。
  1. 上下文 状态快照上下文(Context)是进程运行时CPU状态的完整快照,包含所有寄存器的值。
    上下文\text{上下文}
  2. 通用寄存器组四大元老(RAX/RBX/RCX/RDX)带着RSI/RDI/RSP/RBP一起入册,是快照的核心成员。
    {RAX,RBX,RCX,RDX,RSI,RDI,RSP,RBP}\{ \text{RAX}, \text{RBX}, \text{RCX}, \text{RDX}, \text{RSI}, \text{RDI}, \text{RSP}, \text{RBP} \}
  3. 省略号 更多状态快照还包括标志寄存器、指令指针等,全部打包封装,等待被再次唤醒。
    …\ldots

AI 应用场景

编译器后端寄存器分配

阶段:模型推理优化 / 编译器后端

解决的问题:AI推理引擎(如TensorRT、TVM)需要把深度学习模型部署到CPU上,手动管理成百上千个计算图节点的临时变量。没有一套清晰的寄存器分配策略,生成的机器码会频繁访问内存,推理速度可能下降数倍。

具体案例:TVM编译器在优化矩阵乘算子时,把结果累加器分配给RAX、循环计数分配给RCX、矩阵基址分配给RBX,让内层循环的每一步都发生在CPU内部——就像后厨把常用食材全放在手边,不必每次炒菜都跑去仓库。

操作系统线程上下文切换

阶段:系统软件层 / 并发调度

解决的问题:AI训练任务往往涉及多线程并行,线程切换时CPU必须保存当前线程的寄存器状态,再恢复下一个线程的状态。弄错一个寄存器的保存与恢复,整个训练进程就会崩溃或产生错误结果。

具体案例:Linux内核的switch_to宏在切换线程时,把RAX、RBX、RCX、RDX等所有通用寄存器压入当前线程的内核栈,再从目标线程的内核栈弹出恢复。这就像后厨打烊时把所有厨具归档,第二天开工前原样取出摆放。

逆向工程与AI安全分析

阶段:AI安全 / 二进制漏洞分析

解决的问题:分析恶意AI模型或可疑二进制程序时,安全研究员需要读懂汇编代码,而理解寄存器含义是破译汇编的第一步——RAX里藏着返回值,RCX暴露了循环次数,RDX揭示了乘法运算的扩展位。

具体案例:GDB调试一个崩溃的AI推理程序时,研究员查看RAX寄存器,发现函数返回了一个错误码0xffffffff,瞬间定位到某个特征提取函数在输入越界时没有正确返回0导致的连锁故障。

Python 演示

python76 行
class X86RegisterMachine:    """Minimal x86-64 general register simulator (for teaching)."""     def __init__(self):        self._rax = 0        self._rbx = 0        self._rcx = 0        self._rdx = 0     def load(self, reg, value):        setattr(self, '_' + reg.lower(), value & 0xFFFFFFFFFFFFFFFF)     def store(self, reg):        return getattr(self, '_' + reg.lower())     @property    def eax(self):        return self._rax & 0xFFFFFFFF     @eax.setter    def eax(self, value):        self._rax = value & 0xFFFFFFFF     @property    def ax(self):        return self._rax & 0xFFFF     @ax.setter    def ax(self, value):        self._rax = (self._rax & 0xFFFFFFFFFFFF0000) | (value & 0xFFFF)     @property    def ah(self):        return (self._rax >> 8) & 0xFF     @ah.setter    def ah(self, value):        self._rax = (self._rax & 0xFFFFFFFFFFFF00FF) | ((value & 0xFF) << 8)     @property    def al(self):        return self._rax & 0xFF     @al.setter    def al(self, value):        self._rax = (self._rax & 0xFFFFFFFFFFFFFF00) | (value & 0xFF)     def loop_add(self, n):        """RCX as counter: loop n times, add 10 to RAX each time."""        self._rcx = n        count = 0        while self._rcx != 0:            self._rax += 10            self._rcx -= 1            count += 1        return count     def mul64(self, operand):        """Simulate mul: RAX * operand, result stored in RDX:RAX."""        product = self._rax * operand        self._rdx = (product >> 64) & 0xFFFFFFFFFFFFFFFF        self._rax = product & 0xFFFFFFFFFFFFFFFF        return (self._rdx << 64) | self._rax  if __name__ == '__main__':    m = X86RegisterMachine()    m.load('rax', 0)    n = m.loop_add(5)    print('loop_add iterations:', n, ', RAX =', hex(m.store('rax')), ', RCX =', hex(m.store('rcx')))    m.load('rax', 0xFFFFFFFFFFFFFFFF)    m.eax = 0x12345678    print('After writing EAX, RAX =', hex(m.store('rax')), '(high 32 bits zeroed)')    m.load('rax', 0x100000000)    prod = m.mul64(0x100000000)    print('128-bit product =', hex(prod), ', RDX =', hex(m.store('rdx')), ', RAX =', hex(m.store('rax')))
目的:用Python模拟x86-64通用寄存器的三大核心行为——RCX循环计数递减、写EAX自动清零RAX高32位、RDX:RAX拼接成128位乘法结果。
  1. self._rax & 0xFFFFFFFF取出RAX的低32位,相当于硬件上EAX子寄存器的值。
  2. @eax.setter` 中 `self._rax = value & 0xFFFFFFFF模拟x86-64“写EAX清零RAX高32位”的零扩展规则,这是新手最容易忽略的硬件行为。
  3. (self._rax & 0xFFFFFFFFFFFF0000) | (value & 0xFFFF)写16位AX时,用位掩码保留高48位、只替换低16位。
  4. (self._rax & 0xFFFFFFFFFFFF00FF) | ((value & 0xFF) << 8)写AH时,掩码清掉第8~15位,再把新值左移8位写入。
  5. while self._rcx != 0:模拟loop指令的循环逻辑:RCX不为零就继续迭代,直到减到0退出。
  6. product >> 64取128位乘积的高64位存入RDX,对应硬件上RDX:RAX的高半区。
  7. return (self._rdx << 64) | self._rax把RDX和RAX重新拼回128位,验证乘积还原无误。

常见误区

误区我觉得RAX是x86-64时代全新设计的一个独立寄存器,跟32位的EAX没有关系,旧的32位程序在64位系统上应该完全跑不了。

正解RAX就是EAX的“扩容版”——EAX是RAX的低32位。把RAX想成一个大货架,EAX就是货架最底层的老抽屉。旧程序只要继续操作EAX这个抽屉就能正常运行,这正是x86“向后兼容”的底气所在,也是它纵横几十年的生存之道。

为何常见:人们习惯用“升级换代等于推倒重来”的直觉理解技术演进,但x86走的是“旧城改造”路线,新寄存器是嵌套在旧寄存器外面的加固层。

误区既然RCX叫“计数寄存器”,那它除了给循环数数之外,不能用来存别的数据。

正解RCX只是因为有loop指令和移位指令天然配合它,所以名气和“计数”绑定了。但它本质上是通用寄存器——你也可以往里存地址、存普通数值、做算术。就像厨房里的节拍器,你非要拿它敲核桃定型,也没人拦你。

为何常见:教科书为了讲解方便,给每个寄存器贴了职责标签,学生容易把“典型用途”误解成“唯一用途”。

误区往32位的EAX里写一个新值,那RAX的高32位应该完全不受影响,保持原来的数据。

正解这在ARM64等架构上成立,但x86-64有一条专用的“零扩展”规则:写入任何32位寄存器都会自动把对应64位寄存器的高32位清零。你在EAX里写0x12345678,RAX就变成0x0000000012345678。但是写16位的AX或8位的AL/AH,又确实只影响对应低位——两条规则要分清。

为何常见:因为它是一个“反直觉”的设计决策(为了简化xor清零模式和指令编码),很多人会习惯性地用其他架构的经验套x86,恰好在这里踩坑。

误区函数参数用哪些寄存器传递,全世界统一,学一套就够了。

正解Windows x64和Linux/Unix的System V约定差异很大——比如Windows用RCX、RDX、R8、R9传前4个参数,System V则用RDI、RSI、RDX、RCX、R8、R9。跨平台开发、编写内联汇编时如果混用,轻则参数错乱,重则程序崩溃。

为何常见:很多教材为了简化只讲System V,而读者接触多平台之后才发现“此规矩非彼规矩”,于是产生了认为规则统一的错觉。

面试题

Q1. x86-64 架构中,64 位累加器寄存器的完整名称是?

单选题 · easy

  • A.RAX← 正确答案
  • B.EAX
  • C.AX
  • D.AL

答案:A。RAX是64位累加寄存器,EAX是它低32位的旧版,AX是低16位,AL是最低的8位。好比最大的货架叫RAX,它的抽屉分别叫EAX、AX、AL。

Q2. 32 位子寄存器 EAX 的位宽是?

单选题 · easy

  • A.8 位
  • B.16 位
  • C.32 位← 正确答案
  • D.64 位

答案:C。EAX中的"E"代表Extended,是80386时代从16位扩展到32位的产物。在x86-64时代它被保留为RAX的低32位抽屉,本身依然是32位宽度。

Q3. RCX 在循环指令中最核心的职责是什么?

单选题 · easy

  • A.循环计数递减← 正确答案
  • B.存放内存基址
  • C.保存返回地址
  • D.存储浮点结果

答案:A。RCX(Counter)配合loop指令自动递减并判断是否为零,是循环控制的节拍器。内存基址是RBX的活,返回地址在栈上,浮点结果走XMM寄存器。

Q4. 在 x86-64 模式下,向 EAX 写入数值后,RAX 的高 32 位会?

单选题 · mid

  • A.保持不变
  • B.自动清零← 正确答案
  • C.变成全 1
  • D.取旧值高位

答案:B。x86-64的零扩展规则:写32位寄存器(EAX)会自动把高32位清零。这和写16位AX、8位AL/AH不同——后两者完全不影响其他位。这一设计简化了寄存器清零操作,但也是新手最常见的反直觉坑。

Q5. System V 调用约定中,前两个整数参数分别传入?

单选题 · mid

  • A.RDI 和 RSI← 正确答案
  • B.RAX 和 RBX
  • C.RCX 和 RDX
  • D.R8 和 R9

答案:A。Linux/Unix世界的System V规定前6个整数参数依次放RDI、RSI、RDX、RCX、R8、R9。RAX不参与传参,它专门负责接收返回值——就像后厨只管做菜,上菜窗口有固定的规则。

Q6. 64 位乘法 mul 中,128 位乘积的高 64 位放在哪个寄存器?

单选题 · mid

  • A.RAX
  • B.RBX
  • C.RDX← 正确答案
  • D.RCX

答案:C。mul指令用RDX:RAX拼成128位:高64位放RDX,低64位放RAX。两块大披萨分别装进两个盒子,RDX装上半边、RAX装下半边。除法时RDX则存放余数。

Q7. 以下哪个寄存器不是 x86-64 新增的通用寄存器?

单选题 · hard

  • A.R8
  • B.R12
  • C.R15
  • D.RSP← 正确答案

答案:D。R8~R15是x86-64新增的8个通用寄存器;RSP是从8086时代就有的栈指针寄存器,属于“老四大件”体系的延续,并非新增成员。

Q8. 以下哪些属于 RAX 的子寄存器?

多选题 · easy

  • A.EAX← 正确答案
  • B.AX← 正确答案
  • C.AH← 正确答案
  • D.R15

答案:ABC。EAX(低32位)、AX(低16位)、AH(高8位)、AL(低8位)都是RAX内部嵌套的子寄存器。R15是x86-64新增的独立寄存器,与RAX没有嵌套关系。

Q9. 关于通用寄存器的职责,下列说法正确的有?

多选题 · mid

  • A.RBX 适合存放基地址← 正确答案
  • B.RCX 配合循环指令计数← 正确答案
  • C.RDX 可存除法余数← 正确答案
  • D.RAX 只能存放累加结果

答案:ABC。RBX是基址寄存器,RCX是计数寄存器,RDX在除法中存余数,这三位都有清晰的分工标签。但RAX虽然是累加器,并非“只能”存累加结果——它也能存任意数据,只是约定俗成地承担累加和返回值任务。

Q10. 在 x86-64 中,以下哪些操作不会影响 RAX 的高 32 位?

多选题 · hard

  • A.写入 AL← 正确答案
  • B.写入 AH← 正确答案
  • C.写入 AX← 正确答案
  • D.写入 EAX

答案:ABC。写8位(AL/AH)和16位(AX)子寄存器时,只会修改对应的低位部分,高32位不受影响。唯独写32位的EAX会触发零扩展规则,把RAX的高32位清零——这是x86-64特有的行为,也是面试中最爱挖的“陷阱”。