🔬 CISC vs RISC 架构与汇编指令详解

从底层原理到汇编编程 —— 完整学习指南

📚 第一节:CISC vs RISC 基础概念

CISC Complex Instruction Set Computer

复杂指令集计算机


核心思想:硬件做更多的事,软件(编译器)做更少的事


特点:

• 指令数量多(x86有数千条指令)

• 单条指令功能强大

• 可变长度指令(1-15字节)

• 可以直接操作内存


代表架构:x86、AMD64 (x86-64)

代表厂商:Intel、AMD

RISC Reduced Instruction Set Computer

精简指令集计算机


核心思想:硬件只做简单的事,复杂功能由软件(编译器)组合实现


特点:

• 指令数量少(通常几十条核心指令)

• 每条指令只做一件事

• 固定长度指令(ARM64为32位)

• Load/Store架构(运算只能操作寄存器)


代表架构:ARM、ARM64、RISC-V、MIPS、PowerPC

代表厂商:ARM公司、Apple、Qualcomm

💡 历史背景:
CISC诞生于1950-60年代,当时内存昂贵、编译器技术落后,所以希望用最少的指令完成最多的工作。
RISC诞生于1980年代,随着编译器技术进步,发现把复杂指令拆分成简单指令反而更快更省电。

🍳 厨房类比(帮你理解)

CISC厨师(大厨):一个大厨会做复杂菜肴,一条指令"做鱼香肉丝"就能完成,但你得找专门的大厨。

RISC厨师(快餐):每个厨师只会简单操作(切菜、炒菜、装盘),但有很多厨师,通过配合完成复杂菜肴,效率高且成本低。

🖥️ 第二节:CISC架构详解(以x86-64为例)

1️⃣ CISC设计原则

核心原则:用最少的内存存储程序

早期计算机内存非常昂贵(KB级别),所以设计了复杂的指令,让程序占用的空间更小。


典型特征:

可变长度指令:指令可以是1-15字节,节省空间

复杂寻址模式:一条指令可以包含计算地址的逻辑

内存操作数:指令可以直接读写内存,不需要先加载到寄存器

微代码实现:复杂指令内部被翻译成多个微操作(μop)

2️⃣ CISC指令格式(x86)

x86指令格式(可变长度):
┌────────┬────────┬────────┬────────┬────────┬────────┐
│ 前缀   │ 操作码 │ ModR/M │ SIB    │ 偏移量 │ 立即数 │
│0-4字节 │1-3字节 │1字节  │1字节  │0-4字节 │0-4字节 │
└────────┴────────┴────────┴────────┴────────┴────────┘

示例:
MOV EAX, [EBX+ECX*4+16]   ; 一条指令完成:计算地址 + 加载内存 + 存入寄存器
        

3️⃣ CISC典型指令示例

; x86-64 CISC 典型指令

; 1. 复杂指令:PUSH - 同时完成 减法 + 存储
PUSH RAX          ; RSP -= 8; [RSP] = RAX  (一条指令完成两个操作)

; 2. 复杂指令:ENTER - 建立栈帧
ENTER 32, 0       ; 自动完成:PUSH RBP; MOV RBP,RSP; SUB RSP,32

; 3. 复杂指令:REP MOVSB - 字符串操作
REP MOVSB          ; 重复执行MOVSB,直到RCX=0(一条指令完成循环拷贝)

; 4. 内存直接操作
ADD EAX, [RBX]    ; 直接从内存加载并相加(RISC不允许这样)

; 5. 复杂寻址
MOV EAX, [RBX+RCX*8+16]  ; 一条指令完成:EBX+RCX*8+16 的地址计算 + 内存读取
        
⚠️ CISC的复杂性:
现代x86 CPU(如Intel Core i7/i9)内部其实也是把CISC指令翻译成类似RISC的"微操作(μop)"来执行的。
所以CISC更像是一种"对外接口",内部实现已经RISC化了!

📱 第三节:RISC架构详解(以ARM64为例)

1️⃣ RISC设计原则

核心原则:简单指令快速执行,复杂功能由编译器生成多条指令实现


典型特征:

固定长度指令:ARM64所有指令都是32位(4字节)

Load/Store架构:运算指令只能操作寄存器,内存访问只能用专门的LOAD/STORE指令

大量寄存器:ARM64有31个通用寄存器(x86-64只有16个)

流水线优化:每条指令执行阶段相同,流水线效率高

3操作数指令:ADD X0, X1, X2(结果、源1、源2分开)

2️⃣ RISC指令格式(ARM64)

ARM64指令格式(固定32位):
┌────────┬────────┬────────┬────────┬────────┬────────┬────────┐
│dfsf    │ op     │ Rm     │        │ Rn     │ Rd     │        │
│31 30   │29   24 │20   16 │15   10 │9     5 │4     0 │        │
└────────┴────────┴────────┴────────┴────────┴────────┴────────┘
所有指令都是32位,解码电路非常简单!

示例:
ADD X0, X1, X2   ; 格式固定,硬件一眼就能识别
        

3️⃣ RISC典型指令示例

; ARM64 RISC 典型指令

; 1. 简单指令:每条只做一件事
ADD X0, X1, X2    ; X0 = X1 + X2  (3操作数,结果和源分开)

; 2. Load/Store架构:必须先用LOAD加载
LDR X0, [X1]       ; X0 = 内存[X1]  (从内存加载)
ADD X0, X0, #1     ; X0 = X0 + 1    (在寄存器里运算)
STR X0, [X1]       ; 内存[X1] = X0  (写回内存)
; 注意:ARM64不能直接 ADD [X1], #1

; 3. 分支指令
CMP X0, #10        ; 比较X0和10
B.EQ loop           ; 如果相等,跳转到loop

; 4. 批量加载/存储(RISC的优化)
STP X29, X30, [SP, #-16]!  ; Store Pair: 一次存两个寄存器
LDP X29, X30, [SP], #16   ; Load Pair: 一次加载两个寄存器

; 5. 位移和位运算
LSL X0, X1, #3     ; X0 = X1 << 3  (逻辑左移)
AND X0, X1, #0xFF   ; X0 = X1 & 0xFF(按位与)
        
✅ RISC的优势:
• 指令固定长度 → 解码简单 → 省电 → 适合移动设备
• 大量寄存器 → 减少内存访问 → 更快 → 适合高性能场景
• 编译器优化空间大 → Apple M系列芯片就是最好的证明

📊 第四节:CISC vs RISC 全方位对比

对比维度 CISC (x86-64) RISC (ARM64)
指令数量 多(x86有1000+条指令) 少(ARM64核心指令约100+条)
指令长度 可变(1-15字节) 固定(32位/4字节)
指令复杂度 高(一条指令可完成复杂操作) 低(每条指令只做简单操作)
内存访问 可以直接操作内存(如 ADD EAX,[EBX]) 只能通过LOAD/STORE访问内存
通用寄存器 16个(RAX~R15,部分有专用用途) 31个(X0~X30,全部通用)
指令格式 2操作数:ADD dest, src(结果覆盖dest) 3操作数:ADD rd, rn, rm(结果单独存放)
寻址模式 复杂(支持多种寻址方式) 简单(只有少量寻址模式)
代码密度 高(同样功能,指令数少) 低(同样功能,指令数多)
功耗 高(解码电路复杂,晶体管多) 低(指令简单,解码电路小)
编译器负担 轻(复杂指令由硬件实现) 重(需要编译器生成多条指令)
典型代表 Intel x86/AMD64、VAX、Motorola 68k ARM/ARM64、RISC-V、MIPS、PowerPC

🚗 汽车类比(加深理解)

CISC = 自动挡豪华轿车:操作简单(一条指令搞定),但内部结构复杂(油耗高、维修难)。

RISC = 手动挡跑车:操作稍微复杂(需要配合离合器、换挡),但结构简单(省油、效率高、可玩性高)。

🔬 第五节:x86-64 (CISC) 寄存器详解

1️⃣ 通用寄存器(16个)

x86-64 通用寄存器(64位)

参数传递:
RDI
RSI
RDX
RCX
R8
R9
(前6个参数)
返回值:
RAX
(函数返回值)
被调用者保存:
RBX
RBP
R12
R13
R14
R15
栈指针:
RSP
(Stack Pointer)
特殊用途:
RIP
(指令指针,不能直接修改)

💡 寄存器历史兼容性:

每个64位寄存器都有"小名":

• RAX 的低32位 = EAX,低16位 = AX,低8位 = AL/AH

• 这是为了兼容1978年的8086处理器!

2️⃣ x86-64 寄存器使用约定(System V ABI)

函数调用时参数传递顺序:

第1个参数 → RDI

第2个参数 → RSI

第3个参数 → RDX

第4个参数 → RCX

第5个参数 → R8

第6个参数 → R9

第7个及以后参数 → 压入栈中


返回值:存储在 RAX

被调用者保存(callee-saved):RBX, RBP, R12-R15(函数如果用了这些,必须恢复原来的值)

调用者保存(caller-saved):RAX, RCX, RDX, RSI, RDI, R8-R11(函数可以随时覆盖这些)

3️⃣ x86-64 标志寄存器(RFLAGS)

RFLAGS 标志寄存器(重要位)

CF
进位标志(无符号溢出)
ZF
零标志(结果为0)
SF
符号标志(结果为负)
OF
溢出标志(有符号溢出)
PF
奇偶标志
AF
辅助进位标志
DF
方向标志(字符串操作用)

🔬 第六节:ARM64 (RISC) 寄存器详解

1️⃣ 通用寄存器(31个 + 特殊寄存器)

ARM64 通用寄存器(64位)

参数/返回值:
X0
X1
X2
X3
X4
X5
X6
X7
(前8个参数/返回值)
临时寄存器:
X9
X10
X11
X12
X13
X14
X15
(调用者保存)
被调用者保存:
X19
X20
X21
X22
X23
X24
X25
X26
X27
X28
特殊寄存器:
X29 (FP)
X30 (LR)
SP
XZR
PC

💡 ARM64 寄存器特点:

• X0-X30 都是64位通用寄存器,没有任何专用用途(除X29/X30外)

• W0-W30 是对应的32位视图(操作低32位,高32位清零)

• XZR 是零寄存器(读它得到0,写它无效)—— 这是RISC的巧妙设计!

2️⃣ ARM64 寄存器使用约定(AAPCS64)

函数调用时参数传递:

第1-8个参数 → X0-X7

第9个及以后参数 → 压入栈中


返回值:X0(小返回值)、X0+X1(大返回值)


特殊寄存器说明:

X29 (FP):帧指针(Frame Pointer),指向当前栈帧

X30 (LR):链接寄存器(Link Register),存储返回地址

SP:栈指针(Stack Pointer)

XZR:零寄存器(读取时返回0,写入时忽略)

PC:程序计数器(不能直接修改,只能通过分支指令改变)

3️⃣ ARM64 条件标志寄存器(NZCV)

CPSR 条件标志(NZCV)

N
负数标志(Negative):结果为负时置1
Z
零标志(Zero):结果为0时置1
C
进位标志(Carry):无符号溢出或移位进位时置1
V
溢出标志(Overflow):有符号溢出时置1

ARM64的条件执行:B.EQ, B.NE, B.GT, B.LT 等条件分支指令

💻 第七节:x86-64 (CISC) 汇编编程示例

示例1:简单的函数调用

; C代码:
; int add(int a, int b) {
;     return a + b;
; }

add:
    ; 参数:EDI = a, ESI = b
    ; 返回值:EAX
    
    MOV EAX, EDI       ; EAX = a
    ADD EAX, ESI       ; EAX = EAX + b
    RET                     ; 返回,结果在EAX中

; 注意:x86是2操作数指令,结果会覆盖第一个操作数
        

示例2:if-else 条件判断

; C代码:
; if (a > b) {
;     return a;
; } else {
;     return b;
; }

max:
    ; 参数:EDI = a, ESI = b
    
    CMP EDI, ESI       ; 比较 a 和 b,设置标志位
    JG  .return_a       ; 如果 a > b,跳转到 return_a
    MOV EAX, ESI       ; 否则 EAX = b
    RET
    
.return_a:
    MOV EAX, EDI       ; EAX = a
    RET
        

示例3:循环(计算1到n的和)

; C代码:
; int sum(int n) {
;     int result = 0;
;     for (int i = 1; i <= n; i++) {
;         result += i;
;     }
;     return result;
; }

sum:
    ; 参数:EDI = n
    
    XOR EAX, EAX       ; result = 0  (EAX = 0 的惯用写法)
    XOR ECX, ECX       ; i = 0
    
.loop:
    INC ECX              ; i++
    ADD EAX, ECX       ; result += i
    CMP ECX, EDI       ; 比较 i 和 n
    JLE .loop              ; 如果 i <= n,继续循环
    RET                     ; 返回 result (在EAX中)
        

示例4:栈操作(函数调用栈帧)

; x86-64 函数序言(prologue)和结尾(epilogue)

my_function:
    ; 函数序言:建立栈帧
    PUSH RBP             ; 保存旧的帧指针
    MOV  RBP, RSP       ; 设置新的帧指针
    SUB  RSP, 32          ; 为局部变量分配32字节栈空间
    
    ; ... 函数体 ...
    
    ; 函数结尾:销毁栈帧
    MOV  RSP, RBP       ; 恢复栈指针
    POP  RBP              ; 恢复旧的帧指针
    RET                      ; 返回
        

💻 第八节:ARM64 (RISC) 汇编编程示例

示例1:简单的函数调用

; C代码:
; int add(int a, int b) {
;     return a + b;
; }

add:
    ; ARM64 RISC 版本
    ; 参数:W0 = a, W1 = b
    ; 返回值:W0
    
    ADD W0, W0, W1    ; W0 = W0 + W1  (3操作数,但结果可以覆盖源)
    RET                     ; 返回,结果在W0中

; 更好的写法(不覆盖输入参数):
; ADD W2, W0, W1   ; W2 = W0 + W1
; MOV W0, W2        ; W0 = W2
; RET
        

示例2:if-else 条件判断

; C代码:
; if (a > b) {
;     return a;
; } else {
;     return b;
; }

max:
    ; 参数:W0 = a, W1 = b
    
    CMP W0, W1         ; 比较 a 和 b,设置标志位
    B.GT .return_a        ; 如果 a > b,跳转到 return_a
    MOV W0, W1         ; 否则 W0 = b
    RET
    
.return_a:
    RET                      ; W0 已经是 a,直接返回
        

示例3:循环(计算1到n的和)

; C代码:
; int sum(int n) {
;     int result = 0;
;     for (int i = 1; i <= n; i++) {
;         result += i;
;     }
;     return result;
; }

sum:
    ; 参数:W0 = n
    
    MOV W2, #0          ; result = 0  (W2 = result)
    MOV W3, #1          ; i = 1  (W3 = i)
    
.loop:
    CMP W3, W0         ; 比较 i 和 n
    B.GT .done            ; 如果 i > n,结束循环
    ADD W2, W2, W3    ; result += i
    ADD W3, W3, #1      ; i++
    B   .loop              ; 无条件跳转回 loop
    
.done:
    MOV W0, W2         ; 返回值 = result
    RET
        

示例4:栈操作(函数调用栈帧)

; ARM64 函数序言和结尾(使用STP/LDP优化)

my_function:
    ; 函数序言:建立栈帧
    STP X29, X30, [SP, #-16]!  ; 压入帧指针和返回地址(一次存两个!)
    MOV X29, SP                           ; 设置帧指针
    SUB SP, SP, #32                        ; 分配局部变量空间
    
    ; ... 函数体 ...
    
    ; 函数结尾:销毁栈帧
    ADD SP, X29, #0                         ; 恢复栈指针
    LDP X29, X30, [SP], #16             ; 弹出帧指针和返回地址
    RET                                              ; 返回
        
💡 ARM64 的 STP/LDP 指令:
STP (Store Pair) 和 LDP (Load Pair) 是ARM64的优化指令,可以一次存/取两个寄存器。
这是RISC"用简单指令组合出复杂功能"思想的体现——虽然单条指令简单,但提供有用的组合指令来提高效率。

🎯 第九节:同一功能的 CISC vs RISC 汇编对比

对比1:数组求和

; x86-64 数组求和
; int sum(int* arr, int n)

sum_x86:
    XOR  EAX, EAX       ; result = 0
    XOR  ECX, ECX       ; i = 0
    
.loop:
    CMP  ECX, ESI       ; 比较 i 和 n
    JGE  .done              ; 如果 i >= n,结束
    ADD  EAX, [RDI+RCX*4] ; result += arr[i]
    INC  ECX              ; i++
    JMP  .loop
    
.done:
    RET
            
; ARM64 数组求和
; int sum(int* arr, int n)

sum_arm64:
    MOV  W2, #0          ; result = 0
    MOV  W3, #0          ; i = 0
    
.loop:
    CMP  W3, W1         ; 比较 i 和 n
    B.GE .done            ; 如果 i >= n,结束
    LDR  W4, [X0, X3, LSL #2]  ; W4 = arr[i]
    ADD  W2, W2, W4    ; result += W4
    ADD  W3, W3, #1      ; i++
    B    .loop
    
.done:
    MOV  W0, W2         ; 返回值 = result
    RET
            

🔍 关键差异分析:

1. 内存访问:

• x86-64: ADD EAX, [RDI+RCX*4] —— 可以直接在内存操作数上运算

• ARM64: 必须先 LDR W4, [X0, X3, LSL #2] 加载到寄存器,再运算


2. 寻址模式:

• x86-64: 支持复杂的寻址模式(基址+变址*比例+偏移)

• ARM64: 寻址模式简单,复杂地址计算需要多条指令


3. 代码密度:

• x86-64: 代码更紧凑(同样功能,指令数更少)

• ARM64: 代码更长,但每条指令执行更快

对比2:函数调用(多个参数)

; x86-64 调用有多个参数的函数
; void foo(int a, int b, int c, 
;         int d, int e, int f, 
;         int g);

PUSH 7                ; 第7个参数压栈
MOV  R9, 6           ; 第6个参数 = 6
MOV  R8, 5           ; 第5个参数 = 5
MOV  RCX, 4          ; 第4个参数 = 4
MOV  RDX, 3          ; 第3个参数 = 3
MOV  RSI, 2          ; 第2个参数 = 2
MOV  RDI, 1          ; 第1个参数 = 1
CALL foo
ADD  RSP, 8          ; 清理栈(第7个参数)
            
; ARM64 调用有多个参数的函数
; void foo(int a, int b, int c,
;         int d, int e, int f,
;         int g);

MOV  W0, #1          ; 第1个参数 = 1
MOV  W1, #2          ; 第2个参数 = 2
MOV  W2, #3          ; 第3个参数 = 3
MOV  W3, #4          ; 第4个参数 = 4
MOV  W4, #5          ; 第5个参数 = 5
MOV  W5, #6          ; 第6个参数 = 6
MOV  W6, #7          ; 第7个参数 = 7
BL   foo                ; 调用函数
; ARM64不需要清理栈!所有参数都在寄存器中
            
✅ ARM64参数传递的优势:
• 前8个参数都在寄存器中,速度极快
• 不需要访问内存(栈),功耗更低
• 不需要函数返回后清理栈
• 这就是为什么ARM64在移动设备上更省电的重要原因之一!

📝 第十节:知识测验

测试一下你学了多少!点击选项查看答案。

问题1:CISC和RISC的核心区别是什么?
A. CISC用RISC不用
B. CISC指令复杂、数量多;RISC指令简单、数量少
C. CISC是Intel的,RISC是ARM的
问题2:ARM64的Load/Store架构是什么意思?
A. 只能开车不能走路
B. 运算指令只能操作寄存器,内存访问只能用专门的LOAD/STORE指令
C. 只能从内存加载,不能存储到内存
问题3:x86-64有多少个通用寄存器?ARM64有多少个?
A. x86-64有16个,ARM64有31个
B. 都是16个
C. x86-64有32个,ARM64有16个
问题4:为什么ARM64比x86-64更省电?
A. 因为ARM公司更环保
B. 因为ARM64频率更低
C. 因为指令固定长度、解码简单、寄存器多减少内存访问
问题5:Apple M系列芯片为什么这么强?
A. 因为他们用了RISC架构(ARM64),大量寄存器+优秀的编译器优化
B. 因为Apple的CPU频率更高
C. 因为Apple用了更多核心

🎓 总结:你应该记住的重点

核心要点

1️⃣ CISC = 复杂指令集:指令多、功能强、可变长度、可直接操作内存(x86-64)

2️⃣ RISC = 精简指令集:指令少、功能简单、固定长度、Load/Store架构(ARM64)

3️⃣ 寄存器差异:x86-64有16个,ARM64有31个

4️⃣ 参数传递:x86-64前6个用寄存器,后面用栈;ARM64前8个用寄存器

5️⃣ 功耗:ARM64更省电,适合移动设备;x86-64性能强,适合桌面/服务器

6️⃣ 未来趋势:RISC正在崛起(Apple M系列、AWS Graviton、RISC-V)

🏆 学习建议:
• 如果你想深入理解计算机底层,建议学习ARM64汇编(更简洁,适合学习)
• 如果你要开发PC/服务器程序,需要掌握x86-64汇编
• 推荐实践:用Godbolt Compiler Explorer(https://godbolt.org)查看C代码编译后的汇编!
• 下一步学习:操作系统、编译原理、计算机体系结构

🎉 恭喜你完成了 CISC vs RISC 的学习!

现在你应该能看懂x86-64和ARM64的汇编代码了!