首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 指標與組合語言基礎 (二):堆疊幀、呼叫慣例、參數傳遞與返回值實戰

     編輯:關於C語言

實驗環境

  • 架構:x86-64 (AMD64)
  • OS:Linux
  • 工具:GCC, GDB, objdump
  • 呼叫慣例:System V AMD64 ABI

範例程式:陣列初始化與列印


/* asm_demo.s */
.section .rodata
msg: .asciz "value = %d\n"

.section .data
arr: .fill 10, 4, 0   /* 10 個 int,初始 0 */

.section .text
.global main
main:
    /* 初始化 arr[i] = i */
    mov $0, %ecx              # i = 0
init_loop:
    mov %ecx, arr(,%ecx,4)    # arr[i] = i (基址 + 索引*4)
    inc %ecx
    cmp $10, %ecx
    jne init_loop

    /* 列印陣列 */
    mov $0, %ecx              # i = 0
print_loop:
    mov arr(,%ecx,4), %eax    # eax = arr[i]
    push %ecx                 # 保存 ecx (printf 會破壞)
    push %eax                 # 參數 2: value
    push $msg                 # 參數 1: format string
    call printf
    add $8, %rsp              # 清理參數 (2 個 64-bit = 16 bytes? 實際 8+8=16)
    pop %ecx                  # 還原 ecx
    inc %ecx
    cmp $10, %ecx
    jne print_loop
    ret

關鍵指令解析

1. 記憶體定址模式:`arr(,%ecx,4)`

語法:`位移(基址, 索引, 比例)` → 有效位址 = `位移 + 基址 + 索引 * 比例`

  • 此處:`0 + 0 + %ecx * 4` = `arr + i*4`,完美對應 `int arr[i]`。
  • 比例僅能為 1, 2, 4, 8 (對應 1/2/4/8 bytes 型別)。

2. 堆疊操作:`push` / `pop` / `add $N, %rsp`

  • x86-64 堆疊**16-byte 對齊**要求:`call` 前 `%rsp % 16 == 8` (因返回位址 8 bytes)。
  • `push %rax` 相當於 `sub $8, %rsp; mov %rax, (%rsp)`。
  • `call printf` 後,呼叫者負責清理參數 (`add $16, %rsp` 或 `pop` 兩次)。

3. 暫存器保存慣例

範例中 `printf` 屬 C 庫函數,**會破壞 RCX**,故迴圈計數器 `ecx` 必須 `push` 保存。

4. 參數傳遞 (前 6 個整數/指標參數)


/* 順序:RDI, RSI, RDX, RCX, R8, R9 */
/* 第 7 參數起壓堆疊 */
printf("%d %d %d", a, b, c);  // a→RDI, b→RSI, c→RDX

浮點參數用 XMM0-XMM7。

5. 返回值

  • 整數/指標:RAX (64-bit) / EAX (32-bit)
  • 浮點:XMM0
  • 結構體 ≤ 16 bytes:RAX+RDX 或 XMM0+XMM1;較大由呼叫者分配空間傳隱含指標 (RDI)。

GDB 動態觀察堆疊幀


$ gcc -g asm_demo.s -o asm_demo
$ gdb ./asm_demo
(gdb) break main
(gdb) run
(gdb) info registers rsp rbp rip
(gdb) x/20xg $rsp      # 檢視堆疊內容
(gdb) disas /m         # 交錯顯示源碼與組合
(gdb) stepi            # 單步執行指令

C 語言對應關係驗證


/* c_demo.c */
#include 
int arr[10];
int main() {
    for (int i=0; i<10; ++i) arr[i] = i;
    for (int i=0; i<10; ++i) printf("value = %d\n", arr[i]);
}

$ gcc -Og -S c_demo.c -o c_demo.s
$ diff -u <(grep -A30 'main:' asm_demo.s) <(grep -A30 'main:' c_demo.s)
# 僅差異於暫存器分配、優化細節,核心邏輯一致

常見除錯場景

類別暫存器責任
呼叫者保存RAX, RCX, RDX, RSI, RDI, R8-R11呼叫前若需保留須自行 push
被呼叫者保存RBX, RBP, R12-R15被呼叫者若使用須 push/pop 還原
特殊RSP, RIP堆疊指標、指令指標
現象組合語言線索根因
區域變數值亂碼`sub $N, %rsp` 分配空間,但未初始化C 變數未初始化
函數返回後呼叫者暫存器變了呼叫者保存暫存器未 push違反 ABI 慣例
參數傳遞錯誤第 7 參數未壓堆疊 / 浮點參數用整數暫存器呼叫慣例不符
堆疊損壞`add $N, %rsp` 數量與 `push` 不匹配內聯組合語言/手寫組合錯誤

結語

組合語言是 C 語言**執行實相**的直接投影。掌握定址模式、堆疊幀佈局、暫存器慣例、參數/返回值傳遞、呼叫者/被呼叫者保存分工,能在最底層驗證 C 代碼行為、優化熱點路徑、解決極難複現的 UB 崩潰。建議配合《CSAPP 第 3 章》、《System V AMD64 ABI 文件》、GDB `layout asm` 日常練習。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved