混合编程
汇编语言与高级语言混合编程可以兼顾开发效率和执行效率。本章介绍汇编与 C/C++ 混合编程的方法。
混合编程概述
为什么要混合编程
- 性能优化:关键代码用汇编优化
- 硬件访问:直接操作硬件或特殊指令
- 遗留代码:调用已有的汇编库
- 学习目的:理解编译器生成的代码
混合编程方式
- 内联汇编:在 C/C++ 代码中嵌入汇编
- 外部汇编模块:独立的汇编文件,链接时合并
- 汇编调用 C:汇编程序调用 C 函数
内联汇编
GCC 内联汇编
GCC 使用 asm 关键字进行内联汇编:
基本语法
asm("汇编代码"
: 输出操作数
: 输入操作数
: 被破坏的寄存器
);
简单示例
int add(int a, int b) {
int result;
asm("add %1, %2; mov %2, %0"
: "=r"(result) // 输出
: "r"(a), "r"(b) // 输入
);
return result;
}
约束说明
| 约束 | 含义 |
|---|---|
| r | 任意通用寄存器 |
| m | 内存操作数 |
| i | 立即数 |
| = | 只写 |
| + | 读写 |
完整示例
#include <stdio.h>
// 使用内联汇编实现加法
int add_asm(int a, int b) {
int result;
asm volatile (
"add %2, %1\n\t" // %1 += %2
"mov %1, %0\n\t" // %0 = %1
: "=r"(result) // 输出
: "r"(a), "r"(b) // 输入
: "cc" // 破坏标志寄存器
);
return result;
}
// 使用内联汇编实现乘法
int mul_asm(int a, int b) {
int result;
asm volatile (
"imul %2, %1\n\t"
"mov %1, %0"
: "=r"(result)
: "r"(a), "r"(b)
: "cc"
);
return result;
}
// 使用内联汇编读取 RDTSC
unsigned long long rdtsc() {
unsigned int lo, hi;
asm volatile (
"rdtsc"
: "=a"(lo), "=d"(hi)
);
return ((unsigned long long)hi << 32) | lo;
}
int main() {
printf("10 + 20 = %d\n", add_asm(10, 20));
printf("10 * 20 = %d\n", mul_asm(10, 20));
unsigned long long t1 = rdtsc();
// 一些操作
unsigned long long t2 = rdtsc();
printf("Cycles: %llu\n", t2 - t1);
return 0;
}
使用寄存器变量
void example() {
register int value asm("r12"); // 强制使用 R12 寄存器
value = 100;
asm volatile (
"add $1, %0"
: "+r"(value)
);
printf("Value: %d\n", value);
}
MSVC 内联汇编
Microsoft 编译器使用 __asm 关键字:
// MSVC 内联汇编(仅支持 x86)
int add_asm(int a, int b) {
__asm {
mov eax, a
add eax, b
}
// 返回值在 EAX 中
}
// x64 下 MSVC 不支持内联汇编,需要使用外部汇编或 intrinsics
外部汇编模块
创建汇编模块
汇编文件(add.asm)
; Linux x86-64
section .text
global add_numbers
; int add_numbers(int a, int b, int c)
; 参数:EDI = a, ESI = b, EDX = c
add_numbers:
mov eax, edi
add eax, esi
add eax, edx
ret
C 头文件(add.h)
#ifndef ADD_H
#define ADD_H
int add_numbers(int a, int b, int c);
#endif
C 主程序(main.c)
#include <stdio.h>
#include "add.h"
int main() {
int result = add_numbers(10, 20, 30);
printf("Result: %d\n", result);
return 0;
}
编译链接
# 编译汇编文件
nasm -f elf64 add.asm -o add.o
# 编译 C 文件
gcc -c main.c -o main.o
# 链接
gcc main.o add.o -o program
# 或者一步完成
gcc main.c add.o -o program
复杂示例
汇编文件(math.asm)
section .text
; int sum_array(int *arr, int n)
; 参数:RDI = arr, ESI = n
global sum_array
sum_array:
xor eax, eax ; 累加器
test esi, esi
jz .done
mov ecx, esi ; 计数器
.loop:
add eax, [rdi]
add rdi, 4
loop .loop
.done:
ret
; void reverse_array(int *arr, int n)
; 参数:RDI = arr, ESI = n
global reverse_array
reverse_array:
test esi, esi
jle .done
lea rsi, [rdi + rsi*4 - 4] ; 指向最后一个元素
.loop:
cmp rdi, rsi
jge .done
; 交换元素
mov eax, [rdi]
mov edx, [rsi]
mov [rdi], edx
mov [rsi], eax
add rdi, 4
sub rsi, 4
jmp .loop
.done:
ret
; int find_max(int *arr, int n)
; 参数:RDI = arr, ESI = n
global find_max
find_max:
test esi, esi
jz .empty
mov eax, [rdi] ; 假设第一个元素最大
mov ecx, esi
dec ecx
jz .done
add rdi, 4
.loop:
cmp [rdi], eax
jle .next
mov eax, [rdi]
.next:
add rdi, 4
loop .loop
.done:
ret
.empty:
mov eax, -1
ret
C 主程序
#include <stdio.h>
extern int sum_array(int *arr, int n);
extern void reverse_array(int *arr, int n);
extern int find_max(int *arr, int n);
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int n = sizeof(arr) / sizeof(arr[0]);
printf("Sum: %d\n", sum_array(arr, n));
printf("Max: %d\n", find_max(arr, n));
printf("Original: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
reverse_array(arr, n);
printf("Reversed: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
汇编调用 C 函数
基本调用
C 文件(lib.c)
#include <stdio.h>
void print_message(const char *msg) {
printf("%s\n", msg);
}
int add(int a, int b) {
return a + b;
}
汇编文件(main.asm)
extern print_message
extern add
section .data
message db "Hello from Assembly!", 0
section .text
global _start
_start:
; 调用 print_message
lea rdi, [rel message]
call print_message
; 调用 add
mov edi, 10
mov esi, 20
call add
; EAX = 30
; 退出
mov rax, 60
xor rdi, rdi
syscall
编译链接
gcc -c lib.c -o lib.o
nasm -f elf64 main.asm -o main.o
ld main.o lib.o -o program -lc -dynamic-linker /lib64/ld-linux-x86-64.so.2
使用 C 标准库
extern printf
extern malloc
extern free
section .data
format db "Value: %d", 0xA, 0
section .text
global main
main:
push rbp
mov rbp, rsp
; 分配内存
mov edi, 100
call malloc
mov r12, rax ; 保存指针
; 使用内存
mov dword [r12], 42
; 打印值
lea rdi, [rel format]
mov esi, [r12]
xor eax, eax ; printf 使用 XMM 寄存器时需要清零 AL
call printf
; 释放内存
mov rdi, r12
call free
xor eax, eax
pop rbp
ret
数据交换
结构体传递
C 定义
typedef struct {
int x;
int y;
} Point;
int distance_squared(Point *p);
汇编实现
; int distance_squared(Point *p)
; 参数:RDI = Point 指针
global distance_squared
distance_squared:
mov eax, [rdi] ; x
imul eax, eax ; x * x
mov ecx, [rdi + 4] ; y
imul ecx, ecx ; y * y
add eax, ecx ; x^2 + y^2
ret
数组传递
; void process_array(int *arr, int n)
; 参数:RDI = 数组指针,ESI = 元素个数
global process_array
process_array:
test esi, esi
jz .done
mov ecx, esi
.loop:
; 处理每个元素
imul dword [rdi], 2 ; 乘以 2
add rdi, 4
loop .loop
.done:
ret
调用约定注意事项
Linux x86-64
; 函数调用前确保:
; 1. RSP 16 字节对齐
; 2. 参数正确放置
; 3. 调用者保存寄存器已保存
; 调用 C 函数
mov edi, arg1
mov esi, arg2
mov edx, arg3
mov ecx, arg4
mov r8d, arg5
mov r9d, arg6
; 更多参数入栈
call c_function
Windows x64
; Windows x64 调用约定
; 参数:RCX, RDX, R8, R9,然后栈
; 需要预留 32 字节影子空间
sub rsp, 40 ; 影子空间 + 对齐
mov rcx, arg1
mov rdx, arg2
mov r8, arg3
mov r9, arg4
call c_function
add rsp, 40
编译器生成的汇编
查看编译器输出
# 生成汇编文件
gcc -S -masm=intel -fno-asynchronous-unwind-tables example.c -o example.s
# 使用 objdump 反汇编
objdump -d -M intel program
理解编译器优化
// C 代码
int add(int a, int b) {
return a + b;
}
; 编译器生成的汇编(-O0)
add:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov edx, DWORD PTR [rbp-4]
mov eax, DWORD PTR [rbp-8]
add eax, edx
pop rbp
ret
; 编译器生成的汇编(-O2)
add:
lea eax, [rdi+rsi]
ret
小结
本章介绍了汇编与 C/C++ 混合编程:
- 内联汇编:GCC 和 MSVC 的不同语法
- 外部汇编模块:独立的汇编文件与 C 链接
- 汇编调用 C:正确遵循调用约定
- 数据交换:结构体和数组的传递
- 编译器输出:理解编译器生成的汇编
混合编程是高级编程技术,需要深入理解调用约定和 ABI。下一章将学习 x86 汇编速查表。