前置知识: C

C与汇编交互

00:00
3 min Advanced 2026/6/14

内联汇编与外部汇编

概述

在性能关键的场景(如操作系统内核、驱动程序、加密算法、性能优化)中,C语言可能无法直接利用处理器的特定指令。C与汇编的交互允许开发者在C代码中嵌入汇编指令,或调用外部汇编函数。GCC和Clang使用扩展内联汇编语法,MSVC使用 __asm 语法。

基础概念

为什么需要汇编交互

  • 访问C语言无法直接使用的处理指令(如 cpuidrdtsc
  • 实现极致的性能优化(如SIMD指令)
  • 编写操作系统中与硬件直接交互的代码
  • 实现上下文切换中断处理等底层操作

内联汇编 vs 外部汇编

方式
内联汇编与C代码混合编译器优化寄存器分配语法复杂,不可移植
外部汇编完全控制,独立编译调用约定需手动处理

GCC 内联汇编语法

__asm__ __volatile__ (
    "汇编指令模板"
    : 输出操作数列表    // 可选
    : 输入操作数列表    // 可选
    : 修改的寄存器列表  // 可选
);

快速上手

基本内联汇编

#include <stdio.h>

int main(void) {
    int result;

    // 最简单的内联汇编:将42放入eax寄存器
    __asm__ (
        "movl $42, %0"   // 汇编指令
        : "=r"(result)    // 输出:将寄存器值存入result
    );

    printf("结果: %d\n", result); // 输出: 42
    return 0;
}

读取时间戳计数器

#include <stdio.h>
#include <stdint.h>

// 使用 rdtsc 读取CPU时间戳
static inline uint64_t rdtsc(void) {
    unsigned int lo, hi;
    __asm__ __volatile__ (
        "rdtsc"           // 读取时间戳计数器
        : "=a"(lo), "=d"(hi)  // 低32位存入eax,高32位存入edx
    );
    return ((uint64_t)hi << 32) | lo;
}

int main(void) {
    uint64_t start = rdtsc();
    // 执行一些操作
    for (volatile int i = 0; i < 1000000; i++);
    uint64_t end = rdtsc();

    printf("耗时: %llu 个时钟周期\n", end - start);
    return 0;
}

详细用法

操作数约束

约束字符告诉编译器如何分配寄存器或内存:

约束说明
r通用寄存器
m内存操作数
i立即数
aeax/rax 寄存器
bebx/rbx 寄存器
cecx/rcx 寄存器
dedx/rdx 寄存器
Sesi/rsi 寄存器
Dedi/rdi 寄存器

输入和输出操作数

#include <stdio.h>

int main(void) {
    int a = 10, b = 20, sum;

    // 加法:将a和b相加,结果存入sum
    __asm__ (
        "addl %2, %0"      // %0 += %2
        : "=r"(sum)         // 输出操作数 %0
        : "0"(a), "r"(b)   // 输入操作数 %1=a(与%0同寄存器), %2=b
    );

    printf("sum = %d\n", sum); // 输出: 30
    return 0;
}

修改寄存器列表

告诉编译器哪些寄存器被汇编代码修改了:

#include <stdio.h>

int main(void) {
    int result;
    __asm__ (
        "movl $1, %%eax\n\t"   // 使用 %% 引用寄存器名
        "movl $2, %%ebx\n\t"
        "addl %%ebx, %%eax\n\t"
        "movl %%eax, %0"
        : "=r"(result)
        :
        : "eax", "ebx"          // 告诉编译器eax和ebx被修改
    );

    printf("结果: %d\n", result); // 输出: 3
    return 0;
}

volatile 的作用

__volatile__ 告诉编译器不要优化掉这汇编代码:

// 不加 volatile:编译器可能认为这段汇编没有副作用而删除它
__asm__ ("nop");

// 加 volatile:确保汇编代码不被优化掉
__asm__ __volatile__ ("nop");

// 对于有副作用的汇编(如I/O操作),必须加 volatile
__asm__ __volatile__ ("outb %0, $0x60" : : "a"(data));

常见场景

场景一:CPUID 获取CPU信息

#include <stdio.h>
#include <stdint.h>

// 执行 cpuid 指令
void cpuid(uint32_t leaf, uint32_t *eax, uint32_t *ebx,
           uint32_t *ecx, uint32_t *edx) {
    __asm__ __volatile__ (
        "cpuid"
        : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx)
        : "a"(leaf)
    );
}

int main(void) {
    uint32_t eax, ebx, ecx, edx;

    // 获取厂商字符串
    cpuid(0, &eax, &ebx, &ecx, &edx);

    char vendor[13];
    *(uint32_t *)(vendor)     = ebx;
    *(uint32_t *)(vendor + 4) = edx;
    *(uint32_t *)(vendor + 8) = ecx;
    vendor[12] = '\0';

    printf("CPU厂商: %s\n", vendor);
    return 0;
}

场景二:原子比较交换

#include <stdio.h>
#include <stdbool.h>

// 使用 cmpxchg 指令实现原子比较交换
bool atomic_cas(int *ptr, int expected, int desired) {
    bool result;
    __asm__ __volatile__ (
        "lock cmpxchgl %2, %1\n\t"
        "sete %0"
        : "=r"(result), "+m"(*ptr)
        : "r"(desired), "a"(expected)
        : "memory"
    );
    return result;
}

int main(void) {
    int value = 10;

    if (atomic_cas(&value, 10, 20)) {
        printf("交换成功: value = %d\n", value); // value = 20
    }

    if (!atomic_cas(&value, 10, 30)) {
        printf("交换失败: value 仍为 %d\n", value); // value = 20
    }

    return 0;
}

场景三:内存屏障

#include <stdio.h>

// 编译器内存屏障:防止编译器重排序
#define compiler_barrier() __asm__ __volatile__("" ::: "memory")

// 处理器内存屏障(x86)
#define memory_barrier() __asm__ __volatile__("mfence" ::: "memory")

// 读内存屏障
#define read_barrier() __asm__ __volatile__("lfence" ::: "memory")

// 写内存屏障
#define write_barrier() __asm__ __volatile__("sfence" ::: "memory")

int data = 0;
int flag = 0;

void producer(void) {
    data = 42;
    write_barrier(); // 确保data的写入在flag之前完成
    flag = 1;
}

void consumer(void) {
    read_barrier(); // 确保在读取flag之后才读取data
    if (flag) {
        printf("data = %d\n", data); // 保证输出42
    }
}

注意事项

可移植性

内联汇编编译器扩展,不同编译器语法不同:

// GCC/Clang
__asm__ ("movl $1, %0" : "=r"(result));

// MSVC
__asm { mov eax, 1 }
// 或
__asm mov eax, 1

寄存器保存

内联汇编中修改的寄存器必须在修改列表声明,否则编译器可能使用同一寄存器存储其他变量,导致数据损坏。

AT&T vs Intel 语法

GCC默认使用AT&T语法(在左,目的在右),MSVC使用Intel语法(目的在左,在右):

// AT&T 语法(GCC默认)
// movl 源, 目的
__asm__ ("movl $42, %%eax");

// Intel 语法(可通过 .intel_syntax 切换)
// mov 目的, 源
// mov eax, 42

进阶用法

调用外部汇编函数

// main.c
#include <stdio.h>

// 声明外部汇编函数
extern int add_asm(int a, int b);

int main(void) {
    int result = add_asm(10, 20);
    printf("10 + 20 = %d\n", result);
    return 0;
}
# add_asm.s - x86_64 汇编文件
.globl add_asm
add_asm:
    movl %edi, %eax    # 第一个参数在 edi
    addl %esi, %eax    # 第二个参数在 esi,加到 eax
    ret                 # 返回值在 eax

编译链接

gcc -c add_asm.s -o add_asm.o
gcc main.c add_asm.o -o program

SIMD 指令加速

#include <stdio.h>
#include <stdint.h>

// 使用 SSE 指令加速数组求和
int sum_sse(const int *arr, int n) {
    int result = 0;

    // SSE 可以一次处理4个int
    int i = 0;
    int sse_sum[4] = {0, 0, 0, 0};

    for (; i + 3 < n; i += 4) {
        __asm__ __volatile__ (
            "movdqu %1, %%xmm0\n\t"    // 加载4个int到xmm0
            "movdqu %0, %%xmm1\n\t"    // 加载当前累加值到xmm1
            "paddd %%xmm0, %%xmm1\n\t" // 4路并行加法
            "movdqu %%xmm1, %0"         // 存回累加值
            : "+m"(sse_sum)
            : "m"(arr[i])
            : "xmm0", "xmm1", "memory"
        );
    }

    // 汇总SSE结果
    for (int j = 0; j < 4; j++) result += sse_sum[j];

    // 处理剩余元素
    for (; i < n; i++) result += arr[i];

    return result;
}

int main(void) {
    int arr[12] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    printf("总和: %d\n", sum_sse(arr, 12));
    return 0;
}

使用 C11 _Generic 隐藏平台差异

#include <stdio.h>
#include <stdint.h>

// 读取时间戳:不同架构使用不同指令
static inline uint64_t read_timestamp(void) {
#if defined(__x86_64__) || defined(__i386__)
    unsigned int lo, hi;
    __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi));
    return ((uint64_t)hi << 32) | lo;
#elif defined(__aarch64__)
    uint64_t val;
    __asm__ __volatile__("mrs %0, cntvct_el0" : "=r"(val));
    return val;
#else
    return 0; // 不支持的架构
#endif
}

int main(void) {
    uint64_t ts = read_timestamp();
    printf("时间戳: %llu\n", ts);
    return 0;
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式