跳到主要内容

混合编程

汇编语言与高级语言混合编程可以兼顾开发效率和执行效率。本章介绍汇编与 C/C++ 混合编程的方法。

混合编程概述

为什么要混合编程

  • 性能优化:关键代码用汇编优化
  • 硬件访问:直接操作硬件或特殊指令
  • 遗留代码:调用已有的汇编库
  • 学习目的:理解编译器生成的代码

混合编程方式

  1. 内联汇编:在 C/C++ 代码中嵌入汇编
  2. 外部汇编模块:独立的汇编文件,链接时合并
  3. 汇编调用 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 汇编速查表。