C 與彙編能相互調用的本質:兩者編譯產生相同格式的目標文件(.o),鏈接器將符號引用解析為地址。
gcc -c bar.c -o bar.o、nasm -f elf foo.asm -o foo.o — 產生含符號表、重定位表的目標文件ld -m elf_i386 -o foobar foo.o bar.o — 將 foo.o 中 extern choose 與 bar.o 中 int choose(int,int) 符號匹配,重定位為最終地址section .data
num1st dq 3
num2nd dq 4
section .text
extern choose ; 声明 C 函數
global main
global myprint ; 導出彙編函數供 C 調用
main:
push dword [num2nd] ; 參數壓棧:右到左
push dword [num1st]
call choose ; 調用 C 函數
add esp, 8 ; 清理參數棧
mov ebx, 0 ; exit(0)
mov eax, 1
int 0x80
; 彙編實現的打印函數:void myprint(char *msg, int len)
myprint:
mov edx, [esp+8] ; len
mov ecx, [esp+4] ; msg
mov ebx, 1 ; stdout
mov eax, 4 ; sys_write
int 0x80
ret
#includeextern void myprint(char *msg, int len); // 彙編函數聲明 int choose(int a, int b) { if (a >= b) { myprint("the 1st one", 13); } else { myprint("the 2nd one", 13); } return 0; }
#!/bin/bash # build.sh gcc -m32 -c -o bar.o bar.c nasm -f elf32 -o foo.o foo.asm ld -m elf_i386 -s -o foobar foo.o bar.o ./foobar
the 1st one
| 項目 | 說明 |
|---|---|
| 調用約定 | 32 位 cdecl:參數右向左壓棧,調用者清棧,返回值在 EAX |
| 符號修飾 | C 編譯器不修飾函數名(_cdecl),彙編直接用原名 extern/global |
| 數據訪問 | 彙編用 [esp+offset] 訪問棧參數;C 結構體布局需與彙編定義一致 |
| 系統調用 | Linux x86 用 int 0x80,eax 為調用號(1=exit, 4=write) |
| 64 位差異 | 參數用寄存器 (RDI, RSI, RDX, RCX, R8, R9),syscall 指令,調用號不同 |
# 查看符號表 nm bar.o foo.o # 反彙編可執行文件 objdump -d -M intel foobar # GDB 混合調試 gdb ./foobar (gdb) break choose (gdb) break myprint (gdb) run (gdb) info registers (gdb) x/10i $pc
C 與彙編混合編程的關鍵在於統一調用約定、正確聲明符號、鏈接階段解析地址。掌握此技術,可在性能熱點用彙編優化、對接裸機硬件、分析編譯器生成代碼、編寫啟動代碼/中斷處理等場景發揮關鍵作用。