【C语言】汇编语言与C语言的混合编程

在实际开发过程中大多数的使用情况是C语言与汇编语言混合编程的形式。在C代码中插入汇编语言的方法有内联汇编和内嵌汇编两种,通过插入汇编可以在C程序中实现C语言不能完成的一些工作。例如,在下面几种情况中必须使用内联汇编或嵌入型汇编。

(1)程序中使用饱和算术运算,如SSAT16和USAT16指令。

(2)程序中需要对协处理器进行操作。

(3)在C程序中完成对程序状态寄存器的操作。

当然,熟练运用汇编在项目中需要经历一些编程学习的过程:

(1)熟悉 ARM 汇编指令集(MIPS / RISC-V 类似)

(2)理解 CPU 模式、寄存器组织结构

(3)学会写简单的汇编函数

(4)掌握 C 与汇编的混合语法(GCC + Keil)

(5)应用于实际项目(如裸机 OS、RTOS 调度器)(在实际项目中进行混合开发)

✨ 为什么需要混合编程?

在嵌入式系统、操作系统内核、驱动开发等开发场景中,C语言的抽象能力 + 汇编的底层控制能力组合使用,可以:

(1)提高性能

(2)实现底层硬件操作

(3)控制特殊寄存器或处理器指令

🧱 常用方式:两种混合方法
方法 说明 适用
内联汇编(Inline Assembly) 在 C 函数中直接插入汇编语句 GCC、Keil
外联汇编(External Assembly) 汇编代码写在 .s/.asm 文件中,通过链接使用 模块独立,结构清晰
方式 内联汇编(Inline Assembly) 外联汇编(External Assembly)
📄 定义位置 写在 .c 文件中 写在 .s / .S / .asm 文件中
📌 使用方法 使用 __asmasm 关键字 写标准汇编函数,通过 extern 引用
📦 可读性 嵌在 C 中,代码混杂 汇编与 C 分离,结构清晰
📚 维护性 差(混合逻辑) 高(模块独立)
✅ 优点 快速嵌入一两条汇编指令 可重用、结构清晰、支持复杂逻辑
🚫 缺点 语法复杂、可读性差 编译配置略复杂
⚙️ 支持平台 Keil、GCC(语法不同) 所有主流工具链
💡 典型用途 简单寄存器访问、特殊指令 中断处理、系统调用、启动代码
举个例子:应用场景(🧩 必须使用汇编的典型案例)

(1)使用特殊指令:饱和算术(Saturating Arithmetic)

指令如:SSAT16、USAT16

c 复制代码
SSAT R0, #8, R1   ; 将 R1 的值限制在 8 位有符号范围内,结果存在 R0
  • 作用:防止数值溢出,提高数值稳定性
  • 示例用途:音频信号处理、图像计算

(2)访问协处理器(如 FPU、系统控制寄存器)

某些协处理器(如浮点协处理器 FPU、CP15)不能用 C 语言直接访问,此时需要使用 MRC/MCR 等指令:

c 复制代码
MRC p15, 0, r0, c1, c0, 0  ; 从协处理器读取寄存器

(3)操作程序状态寄存器(Program Status Register)

如 CPSR(当前程序状态寄存器)、SPSR(保存的程序状态寄存器)

c 复制代码
MRS R0, CPSR       ; 读取 CPSR
ORR R0, R0, #0xC0  ; 关闭 IRQ/FIQ
MSR CPSR_c, R0     ; 写回 CPSR
  • 用于控制中断、模式切换、标志位等
  • 典型场景:进入/退出临界区、操作中断屏蔽位

✅ 一、内联汇编(Inline Assembly)

📌 GCC 风格语法(AT&T 语法)
c 复制代码
int result;
__asm__ volatile("mov %1, %0" : "=r" (result) : "r" (123));
c 复制代码
__asm volatile ("MRS %0, cpsr" : "=r" (result));
  • "MRS %0, cpsr":汇编部分
  • : "=r" (result):输出部分,%0 表示第一个输出变量
  • 可加 : : : "memory" 等约束来控制寄存器、内存同步等
📌 Keil 风格语法(ARM 风格)

1、简单指令

c 复制代码
__asm {
    MOV R0, #1
    ADD R0, R0, #2
}

2、函数中调用汇编

c 复制代码
void disable_irq(void) {
    __asm {
        MRS R0, CPSR
        ORR R0, R0, #0x80
        MSR CPSR_c, R0
    }
}

📌 应用场景

操作 PRIMASK、CONTROL、xPSR 等系统寄存器;

插入一两条汇编指令(如 NOP, WFI, BKPT

实现临界区控制、快速运算等

✅ 二、外联汇编(External Assembly)

📄 示例:汇编文件 asm_func.s
c 复制代码
.syntax unified
.cpu cortex-m3
.thumb

.global add_two_numbers
add_two_numbers:
    ADD R0, R0, R1
    BX LR
📄 示例:C 文件 main.c
c 复制代码
#include <stdio.h>
extern int add_two_numbers(int a, int b);

int main() {
    int result = add_two_numbers(10, 20);
    printf("Result = %d\n", result);
}

📌 应用场景

启动文件(如 startup.s

中断服务函数保存上下文

实现软中断、系统调用封装

独立汇编模块,如 DSP 优化函数

二者在实际开发中:

如果你要做关闭中断、读状态寄存器,或是 在 C 中快速插入 1-2 条指令,这类操作推荐使用内联汇编;如果你要写启动代码、上下文切换,或是某个函数全用汇编提速,这类操作推荐使用外联汇编。

🧰 附加工具链支持情况
工具链 / IDE 内联汇编 外联汇编 说明
Keil MDK 支持 __asm .s 文件 ARM 风格
GCC (arm-none-eabi) __asm__ .S 文件 AT&T 风格,推荐 .S
IAR __asm / asm .s87 文件等 支持外部汇编模块

📘 汇编与 C 程序相互调用的 AAPCS 规范

AAPCS(ARM Architecture Procedure Call Standard) 是 ARM ABI 中定义的子程序调用规则,它确保了 C 与汇编之间可以安全互相调用 ------ 不论是不同语言写的函数,还是不同编译器生成的代码,只要都遵守 AAPCS,就可以协同工作。

汇编程序、C 程序相互调用时,要特别注意遵守相应的 AAPCS 规则。

AAPCS 简介

AAPCS 是 ARM 的函数调用规范,规定了:

内容 AAPCS 规则
参数传递 前 4 个参数通过 R0~R3 传递,第 5 个及以上通过栈传递
返回值 通过 R0(或 R0+R1)返回
栈对齐 8 字节对齐
寄存器保存 R0R3R12 调用者保存;R4R11 被调用者保存
返回方式 使用 BX LRPOP {..., PC}
函数入口对齐 通常为 4 字节对齐
浮点参数 若启用 FPU,使用 S0~S15 传递浮点参数

代码示例一:1. 从 C 调用汇编语言函数

📄 1.1 汇编文件asm_add.s

c 复制代码
.syntax unified
    .cpu cortex-m3
    .thumb

    .global add_numbers
    .type add_numbers, %function

add_numbers:
    ADD R0, R0, R1     // R0 = R0 + R1
    BX LR              // 返回结果在 R0

📄 1.2 C 文件main.c

c 复制代码
#include <stdio.h>

// 声明汇编函数:符合 AAPCS 规则
extern int add_numbers(int a, int b);

int main(void) {
    int result = add_numbers(10, 20);
    printf("Result = %d\n", result); // 输出:30
    return 0;
}

AAPCS 保证了:

  • 10 → R0,20 → R1
  • 汇编函数用 R0 + R1 → R0,返回值通过 R0 返回
  • BX LR 返回调用点

代码示例二:2. 从汇编语言调用 C 函数

📄 2.1 C 文件math_func.c

c 复制代码
#include <stdio.h>

// 被汇编调用的 C 函数
int multiply(int a, int b) {
    return a * b;
}

📄 2.2 汇编文件asm_main.s

c 复制代码
.syntax unified
.cpu cortex-m3
.thumb

.global main
.type main, %function

.extern multiply   // 声明外部 C 函数

main:
    MOV R0, #6          // 参数 a = 6
    MOV R1, #7          // 参数 b = 7
    BL multiply         // 调用 C 函数 multiply(a, b)

    // R0 中是返回值 6 * 7 = 42
    BKPT #0             // 断点,结束程序

AAPCS 保证了:

  • 汇编用 R0R1 传参
  • C 函数使用 R0R1 接收参数,结果通过 R0 返回
  • 使用 BL 指令调用函数,链接地址自动保存在 LR
汇编中使用 AAPCS 的技巧

🔒 寄存器保护示例(被调用方)

c 复制代码
.global foo
.type foo, %function

foo:
    PUSH {R4-R7, LR}    // 保存被调用者需要保护的寄存器

    ; 函数体...
    ; 可使用 R4~R7 自由操作

    POP {R4-R7, LR}     // 恢复寄存器并返回
    BX LR

遵守规则:

规则 原因
使用 .global 导出函数名 供 C 链接器识别
使用 .type <name>, %function 指明符号是函数
参数必须使用 R0~R3 否则参数错位
使用 BX LR 返回 避免非法返回地址
保证栈 8 字节对齐 否则有些库函数运行异常(如 printf
不要破坏 R4~R11 寄存器 若修改,必须手动保存并恢复

🧾 附加:GCC 编译指令建议(裸机 Cortex-M)

bash 复制代码
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -g -nostdlib main.c asm_add.s -o main.elf
AAPCS中定义了ARM寄存器使用规则如下

(1)在进行子函数调用时,当子函数中有参数时,子程序间通过寄存器 R0、R1、R2、R3 来传递参数,如果参数多于 4 个,则多出的部分用堆栈传递,被调用的子程序在返回前无须恢复寄存器 R0~R3 的内容。

(2)在子程序中,使用寄存器 R4~R11 来保存局部变量。如果在子程序中使用到了 R4 ~ R11 中的某些寄存器,子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值;对于子程序中没有用到的寄存器则不必进行这些操作。在 Thumb 程序中,通常只能使用寄存器 R4 ~ R7 来保存局部变量。

(3)寄存器 R12 用作子程序间 scratch 寄存器(用于保存 SP,在函数返回时使用该寄存器出栈),记作 ip。在子程序间的连接代码段中常使用这种规则。

(4)寄存器 R13 用作数据栈指针,记作 sp,sp 中存放的是当前使用的栈的栈顶的地址。在子程序中寄存器 R13 不能用作其他用途。寄存器 sp 在进入子程序时的值和退出子程序时的值必须相等。

(5)寄存器 R14 称为连接寄存器,记作 lr。它用于保存子程序的返回地址。如果在子程序中保存了返回地址,寄存器 R14 则可以用作其他用途。

(6)寄存器 R15 是程序计数器,记作 pc。它不能用作其他用途。

在开发中使用混合编程的价值所在:

bash 复制代码
性能优化		精准控制寄存器、减少指令周期
硬件访问		控制器件或寄存器、系统初始化
系统安全		中断屏蔽、临界区保护
内核开发		bootloader、上下文切换、调度器实

本文介绍了 ARM 程序设计的过程与方法,包括汇编语言程序、伪指令的使用、汇编器的使用、汇编和C混合编程等内容。这些内容是嵌入式编程的基础,希望有缘并看到这里的读者掌握,丰富自身技术栈,在这条路中更进一步。

以上,欢迎有从事同行业的电子信息工程、互联网通信、嵌入式开发的朋友共同探讨与提问,我可以提供实战演示或模板库。希望内容能够对你产生帮助!

相关推荐
La Pulga4 小时前
【STM32】定时器编码器接口
c语言·stm32·单片机·嵌入式硬件·mcu
北冥电磁电子智能4 小时前
江协科技STM32学习笔记补充之003 :STM32复位电路的详细分析
stm32·单片机·嵌入式硬件
楼田莉子4 小时前
C++算法专题学习:模拟算法
开发语言·c++·学习·算法·leetcode
麦子邪4 小时前
C语言中奇技淫巧07-使用GCC栈保护选项检测程序栈溢出
linux·c语言·开发语言
Coision.4 小时前
硬件:51单片机
单片机·嵌入式硬件·51单片机
苏言の狗4 小时前
A*(Astar)算法详解与应用
c语言·c++·算法
我认不到你5 小时前
JVM分析(OOM、死锁、死循环)(JProfiler、arthas、jdk调优工具(命令行))
java·linux·开发语言·jvm·spring boot
逼子格5 小时前
【Protues仿真】基于AT89C52单片机的温湿度测量
单片机·嵌入式硬件·定时器·硬件工程师·dht11·温湿度传感器·at89c52
扶尔魔ocy5 小时前
【QT特性技术讲解】QPrinter、QPdf
开发语言·qt