嵌入式开发中的一些关键字

volatile

volatile关键字作用

  1. 禁止编译器优化
    告知编译器该变量可能被“外部因素”修改(如硬件、中断、多线程等),每次访问必须直接读写内存,而非依赖寄存器缓存或优化掉“冗余”操作

  2. 确保内存可见性
    强制程序按代码顺序执行对变量的读写操作,避免因编译器重排指令导致的意外行为。

volatile使用场景

  1. 直接访问硬件寄存器(某个外设等)
1
2
3
4
5
volatile uint32_t *status_reg = (volatile uint32_t*)0x40021000;  

while (*status_reg & 0x01) { // 必须每次从内存读取状态位
// 等待硬件完成操作
}

如果不适用volatile编译器的优化:

  • 缓存到寄存器
    编译器发现 status_reg 的值在循环中没有被修改(代码中未显式修改),于是将 *status_reg 第一次读取的值缓存到寄存器中,后续循环直接复用寄存器值,不再从内存读取。

  • 优化结果
    生成的汇编代码可能如下:

    1
    2
    3
    4
    5
    6
    7
    ; 第一次读取 *status_reg 到寄存器 eax
    mov eax, [0x40021000]

    loop:
    test eax, 0x01 ; 检查寄存器 eax 的值
    jnz exit_loop ; 如果状态位为 1,退出循环
    jmp loop ; 继续循环

    问题:硬件修改了状态寄存器的值,但程序始终读取的是寄存器 eax 中的旧值,导致死循环

  1. 中断服务程序中修改的全局变量

主循环中访问的全局变量可能被 ISR 异步修改,即可能同时访问,需用 volatile 确保主循环能感知变化。

1
2
3
4
5
6
7
8
9
10
11
volatile bool data_ready = false;  

void ISR() {
data_ready = true; // 中断触发时修改
}

int main() {
while (!data_ready) { // 必须每次检查内存中的值
// 等待数据就绪
}
}
  1. 多线程/多任务共享变量

在无锁或简单同步的场景中,volatile 可防止编译器优化掉对共享变量的访问。但需注意:volatile 不保证原子性,复杂场景仍需结合锁或原子操作。

  1. 防止编译器优化空循环

在延时或等待硬件响应的循环中,编译器可能优化掉“无意义”的空循环。

1
2
volatile int i;  
for (i = 0; i < 1000; i++); // 禁止优化为无操作

extern “C”

在嵌入式开发中,extern "C" {} 的作用是告诉 C++ 编译器以 C 的方式来处理括号中的代码,从而防止 C++ 的名称修饰(Name Mangling)

使用场景

当你在 C++ 源文件中调用 C 语言写的函数或头文件 时,就需要用 extern "C" 来保证链接正常。

为什么需要 extern "C"

C++ 支持函数重载,因此会对函数名进行“名称修饰”,比如 void foo(int) 可能会变成 _Z3fooi

而 C 编译器不会做名称修饰,函数名就是原始的,比如 foo
所以如果你直接在 C++ 里调用 C 的函数,会因为找不到名字而链接失败。

image-20250414144217089

因为源文件是CPP

使用方法

  1. 使用方法1(不常用):用C写的头文件,在C++中使用,分开

image-20250414143652533

2.使用方法2(推荐使用):直接将extern “C”放在头文件中

image-20250414143805517

1
2
3
4
5
6
7
8
9
10
#ifdef __cplusplus
extern "C"{
#endif


//C风格的函数

#ifdef __cplusplus
}
#endif

__cplusplus 是一个由 C++ 编译器自动定义的宏,表示当前代码是用 C++ 编译器在编译。

  • 如果是 C++ 编译器#ifdef __cplusplus 成立,里面的内容会被编译。
  • 如果是 C 编译器__cplusplus 没有定义,内容就会被忽略。