資料搬移與算術運算 (Data Movement & Arithmetic)
Overview Table
| 主題 | 小節 (書頁) | 核心重點 |
|---|---|---|
| 整數暫存器 | 3.4 (p.215-216) | 16 個 64-bit 通用暫存器,可以 1/2/4/8 bytes 存取低位部分 |
| Operand 形式 | 3.4.1 (p.216-218) | Immediate / Register / Memory;最一般化定址 Imm(rb,ri,s) |
| mov 家族 | 3.4.2 (p.218-222) | movb/w/l/q、movabsq、movz*(零延伸)、movs*(符號延伸)、cltq |
| 搬移範例 | 3.4.3 (p.222-225) | pointer = address;dereference = 把位址放暫存器再做記憶體參照 |
| Push / Pop | 3.4.4 (p.225-227) | stack 向低位址成長;pushq = 先減 %rsp 再寫入;popq 相反 |
| leaq | 3.5.1 (p.227-229) | 只算 effective address 不碰記憶體;編譯器拿來做 a + b*s + Imm 算術 |
| 一元/二元運算 | 3.5.2 (p.230) | inc/dec/neg/not;add/sub/imul/xor/or/and(D ← D op S) |
| 位移 | 3.5.3 (p.230-231) | sal=shl;sar 算術右移 vs shr 邏輯右移;移位量取 %cl 低 m 位 |
| 討論 | 3.5.4 (p.232) | 多數指令 signed/unsigned 通用;只有右移需要區分 |
| 特殊算術 | 3.5.5 (p.233-236) | 單運算元 mulq/imulq 產生 128-bit 積;cqto + idivq / 清零 %rdx + divq |
3.4 存取資訊:16 個整數暫存器 (p.215-216)
x86-64 CPU 有 16 個 64-bit 通用暫存器,名稱皆以 %r 開頭,存放整數與指標。歷史演進:8086 的 8 個 16-bit 暫存器(%ax–%bp)→ IA32 擴為 32-bit(%eax–%ebp)→ x86-64 擴為 64-bit(%rax–%rbp)並新增 %r8–%r15。指令可操作各暫存器的低位 1、2、4、8 bytes。
63 31 15 7 0
+---------------+-----------+-------+------+
| %rax (64-bit) | <- movq 存取全部
| | %eax (32-bit) | <- movl(高 4 bytes 清 0!)
| | | %ax (16-bit)| <- movw(其餘 bytes 不變)
| | | | %al | <- movb(其餘 bytes 不變)
+---------------+-----------+-------+------+
| 64-bit | 32-bit | 16-bit | 8-bit | 慣例角色 |
|---|---|---|---|---|
%rax |
%eax |
%ax |
%al |
回傳值 |
%rbx |
%ebx |
%bx |
%bl |
Callee saved |
%rcx |
%ecx |
%cx |
%cl |
第 4 參數 |
%rdx |
%edx |
%dx |
%dl |
第 3 參數 |
%rsi |
%esi |
%si |
%sil |
第 2 參數 |
%rdi |
%edi |
%di |
%dil |
第 1 參數 |
%rbp |
%ebp |
%bp |
%bpl |
Callee saved |
%rsp |
%esp |
%sp |
%spl |
Stack pointer |
%r8 / %r9 |
%r8d / %r9d |
%r8w / %r9w |
%r8b / %r9b |
第 5 / 6 參數 |
%r10 / %r11 |
%r10d / %r11d |
%r10w / %r11w |
%r10b / %r11b |
Caller saved |
%r12–%r15 |
%r12d–%r15d |
%r12w–%r15w |
%r12b–%r15b |
Callee saved |
- 產生 1 或 2 bytes 的指令:目的暫存器其餘 bytes 保持不變。
- 產生 4 bytes 的指令:高 4 bytes 一律清為 0(x86-64 從 IA32 擴充時採用的慣例)。
%rsp最特殊:指向 run-time stack 的頂端位置,部分指令隱含讀寫它;其餘 15 個用途較彈性,但受標準程式慣例(stack 管理、傳參、回傳值、暫存資料)約束。- 參數傳遞順序:
%rdi, %rsi, %rdx, %rcx, %r8, %r9,回傳值在%rax(細節見 03-Machine-Level-Programs/04-Procedures-and-the-Stack)。
3.4.1 Operand Specifiers:三類運算元與定址模式 (p.216-218)
運算元(operand)有三型:immediate(常數,ATT 格式寫 $Imm,如 $-577、$0x1F;不同指令允許不同範圍,assembler 自動選最精簡編碼)、register(R[ra],把暫存器組視為以識別碼索引的陣列 R)、memory reference(依計算出的 effective address 存取記憶體,記為 M[Addr];b-byte 值寫作
| Type | Form | Operand value | 名稱 |
|---|---|---|---|
| Immediate | $Imm |
Imm | Immediate |
| Register | ra |
R[ra] |
Register |
| Memory | Imm |
M[Imm] |
Absolute |
| Memory | (ra) |
M[R[ra]] |
Indirect |
| Memory | Imm(rb) |
M[Imm + R[rb]] |
Base + displacement |
| Memory | (rb,ri) |
M[R[rb] + R[ri]] |
Indexed |
| Memory | Imm(rb,ri) |
M[Imm + R[rb] + R[ri]] |
Indexed |
| Memory | (,ri,s) |
M[R[ri]*s] |
Scaled indexed |
| Memory | Imm(,ri,s) |
M[Imm + R[ri]*s] |
Scaled indexed |
| Memory | (rb,ri,s) |
M[R[rb] + R[ri]*s] |
Scaled indexed |
| Memory | Imm(rb,ri,s) |
M[Imm + R[rb] + R[ri]*s] |
Scaled indexed |
- 最一般形式
Imm(rb,ri,s)的 effective address 公式: - scale factor s 只能是 1, 2, 4, 8;base 與 index 必須是 64-bit 暫存器。
- 其他形式都是此一般式省略某些成分的特例;常用於存取陣列/結構元素(見 03-Machine-Level-Programs/05-Arrays-and-Data-Structures)。
3.4.2 資料搬移指令 (p.218-222)
mov class 是最常用的指令類:movb(1B)、movw(2B)、movl(4B)、movq(8B),把資料由來源複製到目的地,不做任何轉換。來源可為 immediate / register / memory;目的可為 register / memory。
來源 -> 目的 的 5 種合法組合:
Imm --> Reg movl $0x4050,%eax
Imm --> Mem movb $-17,(%rsp)
Reg --> Reg movw %bp,%sp
Reg --> Mem movq %rax,-12(%rbp)
Mem --> Reg movb (%rdi,%rcx),%al
Mem -X-> Mem 禁止!需經暫存器分兩步:load -> store
- 兩個 operand 不可同時是 memory;memory-to-memory 複製需兩條指令(先 load 進暫存器再 store)。
- 暫存器 operand 的大小必須與指令字尾(
b/w/l/q)一致。 movq的 immediate 來源只能是 32-bit two's-complement,會符號延伸成 64-bit;任意 64-bit immediate 要用movabsq I, R(目的地只能是暫存器)。
movl 例外:目的地為暫存器時,movl 會把高 4 bytes 設為 0(p.219)。示範(p.220 Aside):
movabsq $0x0011223344556677, %rax %rax = 0011223344556677
movb $-1, %al %rax = 00112233445566FF (其餘不變)
movw $-1, %ax %rax = 001122334455FFFF (其餘不變)
movl $-1, %eax %rax = 00000000FFFFFFFF (高 4B 清零!)
movq $-1, %rax %rax = FFFFFFFFFFFFFFFF
零延伸與符號延伸(小 → 大複製)
| Class | Effect | 成員 | 來源 → 目的 |
|---|---|---|---|
movz S, R |
R ← ZeroExtend(S) |
movzbw, movzbl, movzwl, movzbq, movzwq |
reg/mem → reg |
movs S, R |
R ← SignExtend(S) |
movsbw, movsbl, movswl, movsbq, movswq, movslq |
reg/mem → reg |
cltq |
%rax ← SignExtend(%eax) |
無 operand | 等價 movslq %eax,%rax 但編碼更短 |
- 指令名最後兩字元:第一個是來源大小、第二個是目的大小(如
movzbq= byte → quad word,零延伸)。 - 沒有
movzlq!4-byte → 8-byte 零延伸直接用movl(利用「產生 32-bit 值即清高 4 bytes」的性質);符號延伸則三種來源大小(b/w/l)到 64-bit 皆有支援。 - 三種 byte 搬移的差異(p.222 Aside),設
%rax = 0011223344556677、%dl = AA:movb %dl,%al→%rax = 00112233445566AA(只改 1 byte)movsbq %dl,%rax→%rax = FFFFFFFFFFFFFFAA(0xAA 最高位為 1,符號延伸填 FF)movzbq %dl,%rax→%rax = 00000000000000AA(一律填 0)
3.4.3 資料搬移範例 (p.222-225)
long exchange(long *xp, long y) { exchange: xp in %rdi, y in %rsi
long x = *xp; movq (%rdi), %rax 取 *xp,設為回傳值
*xp = y; movq %rsi, (%rdi) 把 y 寫入 xp 指向處
return x; ret
}
- C 的 pointer 就是位址:dereference = 把指標複製進暫存器,再用該暫存器做 memory reference(indirect 定址)。
- 區域變數常放在暫存器而非記憶體——暫存器存取遠快於記憶體。
&(address-of)產生指標;*做 dereference,讀(右值)寫(左值)皆可;把指標傳入函式即可讓函式修改遠端位置的資料(p.224 New to C)。- 帶大小與 signedness 同時轉換的 cast:先改大小、再改 signedness(p.224,呼應 02-Information-Representation/02-Integer-Representations 2.2.6)。
3.4.4 Push 與 Pop (p.225-227)
Stack 是 LIFO(last-in, first-out)結構:push 加入、pop 移除,pop 出的一定是最近 push 且仍在 stack 上的值。x86-64 的 program stack 存於記憶體某區域,向低位址成長——stack 頂端元素的位址是所有元素中最低的;%rsp 保存頂端元素位址。習慣上把 stack 圖畫成上下顛倒(top 在下)。
| 指令 | Effect | 說明 |
|---|---|---|
pushq S |
R[%rsp] ← R[%rsp] − 8; M[R[%rsp]] ← S |
先減 8 再寫入 |
popq D |
D ← M[R[%rsp]]; R[%rsp] ← R[%rsp] + 8 |
先讀出再加 8 |
初始 pushq %rax popq %rdx
%rax 0x123 %rax 0x123 %rax 0x123
%rdx 0 %rdx 0 %rdx 0x123
%rsp 0x108 %rsp 0x100 %rsp 0x108
高位址 (stack bottom)
| ... | | ... | | ... |
0x108 |___________|<-rsp |___________| 0x108 |___________|<-rsp
(top) 0x100 |__0x123____|<-rsp |__0x123____| <- 值仍留在記憶體,
低位址 (成長方向 ↓) (new top) 但已不屬於 stack
pushq %rbp等價於subq $8,%rsp+movq %rbp,(%rsp),但 pushq 機器碼只需 1 byte(兩指令組合共 8 bytes)。popq %rax等價於movq (%rsp),%rax+addq $8,%rsp。- pop 後值仍留在原記憶體位置直到被覆寫;stack top 永遠以
%rsp為準。 - stack 與程式碼、其他資料同在一個記憶體,可用標準定址存取 stack 內任意位置:
movq 8(%rsp),%rdx取 stack 第二個 quad word。
3.5 算術與邏輯運算總表 (p.227-228)
分四組:load effective address、unary、binary、shift。除 leaq 外皆為指令類(各有 b/w/l/q 四種大小,如 add 類 = addb/addw/addl/addq)。
| 指令 | Effect | 說明 |
|---|---|---|
leaq S, D |
D ← &S |
Load effective address(無其他大小變體) |
inc D / dec D |
D ← D+1 / D ← D−1 |
遞增 / 遞減 |
neg D / not D |
D ← -D / D ← ~D |
取負 / 位元反相 |
add S, D |
D ← D + S |
加 |
sub S, D |
D ← D − S |
從 D 減去 S(注意順序) |
imul S, D |
D ← D * S |
乘(截斷成 operand 大小) |
xor / or / and S, D |
D ← D ^ / | / & S |
位元運算 |
sal k, D / shl k, D |
D ← D << k |
左移(兩者相同,右側補 0) |
sar k, D |
D ← D >>A k |
算術右移(補符號位) |
shr k, D |
D ← D >>L k |
邏輯右移(補 0) |
leaq 與 mov 類指令不會改動 condition codes(leaq 專用於位址計算,p.238)。3.5.1 Load Effective Address:leaq (p.227-229)
leaq 形式上像「從記憶體讀到暫存器」的 movq 變體,但完全不存取記憶體——它把第一運算元的 effective address 直接複製到目的地(即 C 的 &S)。用途:(1) 產生之後要用的指標;(2) 精簡表達算術:一條指令算出
- 例:
%rdx存 x,則leaq 7(%rdx,%rdx,4), %rax→%rax = 5x + 7。 - 目的地必須是暫存器;編譯器常把 leaq 用在與定址完全無關的巧妙運算上。
- 編譯範例
scale(p.229):t = x + 4*y + 12*z用三條 leaq 完成:
leaq (%rdi,%rsi,4), %rax x + 4*y
leaq (%rdx,%rdx,2), %rdx z + 2*z = 3*z
leaq (%rax,%rdx,4), %rax (x+4*y) + 4*(3*z) = x + 4*y + 12*z
Imm + base + index*s 的算術式再對照 C 程式。3.5.2 一元與二元運算 (p.230)
- Unary(
inc/dec/neg/not):唯一 operand 同時是來源與目的,可為 register 或 memory。如incq (%rsp)直接把 stack 頂端 8-byte 值 +1。語法對應 C 的++/--。 - Binary(
add/sub/imul/xor/or/and):第二 operand 既是來源也是目的,對應 C 的複合指定(x -= y)。subq %rax,%rdx是%rdx = %rdx − %rax(讀作「subtract %rax from %rdx」),非交換運算尤其易錯。- 第一 operand 可為 imm/reg/mem;第二可為 reg/mem;兩者不可同為 memory(同 mov 限制)。
- 目的為 memory 時,處理器需 read → op → write back 三步。
3.5.3 位移運算 (p.230-231)
移位量在前、被移值在後。移位量可為 immediate 或單一 byte 暫存器 %cl——這是少數只允許特定暫存器當 operand 的指令。目的 operand 可為 register 或 memory。
%cl 的低 m 位,其中
%cl = 0xFF 時:salb 移 7、salw 移 15、sall 移 31、salq 移 63。
- 左移:
sal=shl(效果完全相同,右側補 0)。 - 右移:
sar(arithmetic,補符號位)vsshr(logical,補 0),對應 C 中 signed vs unsigned 右移(位元行為見 02-Information-Representation/01-Information-Storage-and-Bits)。
3.5.4 討論:signed / unsigned 通用性 (p.232)
Figure 3.10 中絕大多數指令對 unsigned 與 two's-complement 運算通用,只有右移需要區分 signed(sar)/ unsigned(shr)版本——這正是 two's-complement 成為 signed 整數首選實作方式的原因之一(呼應 02-Information-Representation/03-Integer-Arithmetic)。
範例 arith(p.232):C 的 z*48 被編譯成 leaq (%rdx,%rdx,2),%rax(×3)+ salq $4,%rax(×16);同一暫存器 %rax 依序承載 3*z、48*z、t4——編譯器會讓單一暫存器輪流存放多個程式值,並在暫存器間搬移。
xorq %rcx,%rcx 是慣用「清零」寫法(x ^ x = 0),等效 movq $0,%rcx 但編碼更短(Practice Problem 3.11)。3.5.5 特殊算術:128-bit 乘除 (p.233-236)
兩個 64-bit 數相乘可能需 128 bits 表示(見 2.3)。Intel 稱 16-byte 量為 oct word;x86-64 以暫存器對 %rdx(高 64 bits):%rax(低 64 bits) 合成一個 128-bit 值,提供有限的 128-bit 支援。
| 指令 | Effect | 說明 |
|---|---|---|
imulq S(單運算元) |
R[%rdx]:R[%rax] ← S × R[%rax] |
Signed 全乘法(128-bit 積) |
mulq S |
R[%rdx]:R[%rax] ← S × R[%rax] |
Unsigned 全乘法 |
cqto |
R[%rdx]:R[%rax] ← SignExtend(R[%rax]) |
轉成 oct word;Intel 文件稱 cqo(ATT 與 Intel 名稱少見的不一致) |
idivq S |
R[%rax] ← R[%rdx]:R[%rax] ÷ S; R[%rdx] ← R[%rdx]:R[%rax] mod S |
Signed 除法:商 → %rax、餘數 → %rdx |
divq S |
同上 | Unsigned 除法 |
imulq有兩種形態:雙運算元(Figure 3.10,積截斷成 64-bit,實作*u64與*t64;截斷後 signed / unsigned 位元行為相同)與單運算元(128-bit 全積);assembler 以 operand 個數區分。- 全乘法:一個因數必須放
%rax,另一個為指令 source operand;積存於%rdx:%rax。 - 128-bit 積存回記憶體需兩條
movq:低 8B 存(%rdi)、高 8B 存8(%rdi)——little-endian,高位 bytes 放較高位址(p.235store_uprod範例)。 - 除法前準備 128-bit dividend(常見情況:被除數只有 64-bit,放
%rax):- Signed:執行
cqto,把%rax符號位複製滿整個%rdx(無 operand,隱含使用)。 - Unsigned:事先把
%rdx清為 0。
- Signed:執行
signed 除法 x / y 的資料流(remdiv, p.236):
%rdx (高 64) %rax (低 64)
cqto: [sign bits of x][ x ] <- 128-bit dividend
| idivq S(除數 = S)
v
%rdx = 餘數 (x mod S) %rax = 商 (x ÷ S)
remdiv範例:因%rdx被除法徵用,原本放在%rdx的參數qp必須先搬到別的暫存器(movq %rdx,%r8),再movq %rdi,%rax→cqto→idivq %rsi,最後把%rax(商)、%rdx(餘)分別存回*qp、*rp。
Exam/Test Patterns
| 情境 / 關鍵字 | 答案 |
|---|---|
| 給定 reg/mem 值,求 operand 值(PP 3.1) | 先算 EA = Imm + R[rb] + R[ri]·s 再查 M[EA];有 $ 是常數本身、無括號純數字是 Absolute 位址 |
mov 該用哪個字尾?(PP 3.2) |
看暫存器 operand 的大小:%eax→l、%dx→w、%bl→b、%rax→q;memory operand 不決定大小 |
| 組譯錯誤判斷(PP 3.3) | 常見錯:兩 operand 皆 memory、字尾與暫存器大小不符、目的地是 immediate、32-bit 暫存器當定址 base |
movl 寫入暫存器後高 4 bytes? |
一律變 0;movb / movw 則保留原值 |
| 4-byte → 8-byte 零延伸 | 不存在 movzlq,直接用 movl reg,reg |
*dp = (dest_t)*sp 選指令(PP 3.4) |
小→大:signed 來源用 movs、unsigned 來源用 movz;大→小:mov 低位部分;先改大小再改 signedness |
leaq k(%ra,%rb,s), %rax 求運算式(PP 3.6/3.7) |
值 = k + ra + rb·s(純算術,不讀記憶體) |
subq %rax,%rdx 的效果 |
%rdx = %rdx − %rax(source 在前、destination 在後) |
%cl = 0xFF 時 salq 移幾位?(3.5.3) |
63(取低 6 位,因 2^6 = 64) |
xorq %rcx,%rcx 的目的(PP 3.11) |
清零(x^x=0),比 movq $0,%rcx 編碼短 |
| signed 除法指令序列(PP 3.12 對照) | movq x,%rax → cqto → idivq y;unsigned 版改為 %rdx 清 0 → divq |
| 128-bit 積如何存放 | %rdx(高 64):%rax(低 64);存入記憶體時低 8B 在低位址(little-endian) |
pushq / popq 後 %rsp? |
push 減 8、pop 加 8(stack 向低位址成長);pushq 等價 sub+mov 但僅 1 byte 編碼 |
Related Notes
- 03-Machine-Level-Programs/01-Program-Encodings-and-Data-Formats — 指令字尾與資料大小(b/w/l/q)的來源
- 03-Machine-Level-Programs/03-Control-Flow — 3.6 起:condition codes 與跳躍指令
- 03-Machine-Level-Programs/04-Procedures-and-the-Stack — push/pop 與參數傳遞慣例的完整應用
- 03-Machine-Level-Programs/05-Arrays-and-Data-Structures — scaled indexed 定址在陣列存取的應用
- 02-Information-Representation/03-Integer-Arithmetic — 截斷乘法、算術/邏輯右移的位元層行為
- 02-Information-Representation/02-Integer-Representations — 符號/零延伸與 casting 順序規則