目录

1.  volatile关键字的作用

1.1  防止编译器优化(确保变量每次从内存读取)

1.2  确保中断服务程序(ISR)和主程序之间的数据同步

1.3  防止多线程(RTOS)环境下的变量优化

1.4  防止编译器移除"无用"代码

2.  static关键字的作用

2.1  修饰局部变量(函数内部)

2.2  修饰全局变量(文件作用域)

2.3  修饰函数

3.  extern关键字的作用

3.1  声明外部全局变量

3.2  声明外部函数

3.3  注意事项

3.4  extern vs static

4.  const关键字的作用

4.1  定义变量为常量

4.2  修饰函数的参数

4.3  修饰指针

4.3.1  指针指向的数据不可变

4.3.2  指针本身不可变(常量指针)

4.3.3  指针和指向的数据都不可变

4.4  在嵌入式开发中的特殊用途

4.4.1  定义只读硬件寄存器

4.4.2  节省Flash存储(常量数据放ROM)

4.4.3   提高代码安全性(防止意外修改)


1.  volatile关键字的作用

1.1  防止编译器优化(确保变量每次从内存读取)

        在嵌入式系统中,某些变量可能会被硬件(如外设寄存器)或中断服务程序(ISR)修改,但编译器无法识别这些修改,可能会错误地优化掉对该变量的访问。

1.2  确保中断服务程序(ISR)和主程序之间的数据同步

        在中断驱动的系统中,主程序可能读取一个变量,而ISR会修改它。如果没有volatile,编译器可能会缓存该变量,导致主程序看不到ISR的修改。举个例子:

//如果data_ready没有volatile,编译器可能优化成if (false),导致程序无法正常工作。

volatile uint8_t data_ready = 0;

//中断服务函数
void UART_ISR() 
{
    data_ready = 1;  // 中断触发,设置标志位
}

int main()
{
    while (1) 
    {
        if (data_ready) // 必须用volatile,否则可能被优化
        {  
            process_data();
            data_ready = 0;
        }
    }
}

1.3  防止多线程(RTOS)环境下的变量优化

        在嵌入式RTOS(如FreeRTOS、RT-Thread)中,多个任务可能共享变量,volatile可以确保变量不会被错误优化。

//如果没有volatile,Task2可能看不到Task1的修改。
volatile uint32_t shared_counter = 0;

void Task1(void *pvParameters) 
{
    while (1) 
    {
        shared_counter++;  // 任务1修改
    }
}

void Task2(void *pvParameters) 
{
    while (1) 
    {
        printf("Counter: %lu\n", shared_counter);  // 任务2读取
    }
}

1.4  防止编译器移除"无用"代码

        某些嵌入式代码(如延时循环)可能被编译器优化掉,volatile可以强制保留。

void delay_ms(uint32_t ms) 
{
    volatile uint32_t i;
    for (i = 0; i < ms * 1000; i++);  // 防止被优化
}

2.  static关键字的作用

        简单来说就是控制变量的存储位置和访问范围。

2.1  修饰局部变量(函数内部)

        在函数体,只会被初始化一次,一个被声明为静态的变量在这一函数被调用过程中维持其值不变,静态变量具有静态存储周期,只会被初始化一次,且在函数调用结束后其值不会丢失,而是保持到下一次函数调用:

#include<stdio.h> 

void counter() 
{
    static int count = 0;  // 只初始化一次
    count++;
    printf("Count: %d\n", count);
}

int main() 
{
    counter();  // 输出 "Count: 1"
    counter();  // 输出 "Count: 2"
    counter();  // 输出 "Count: 3"
    return 0;
}

        我们将static删除可以发现,每次调用就被初始化:

#include<stdio.h> 

void counter() 
{
    int count = 0;  // 只初始化一次
    count++;
    printf("Count: %d\n", count);
}

int main() 
{
    counter();  // 输出 "Count: 1"
    counter();  // 输出 "Count: 2"
    counter();  // 输出 "Count: 3"
    return 0;
}

2.2  修饰全局变量(文件作用域)

        限制全局变量仅在当前文件内可见(防止被其他文件extern引用)。

// file1.c
static int private_var = 42;  // 仅file1.c可见

void print_var() {
    printf("%d\n", private_var);  // 可以访问
}
// file2.c
extern int private_var;  // 错误!无法访问file1.c的static变量

2.3  修饰函数

        限制函数仅在当前文件内可见(类似static全局变量)。

// utils.c
static void internal_func() {  // 仅utils.c可用
    printf("This is a private function.\n");
}

void public_func() {  // 可以被其他文件调用
    internal_func();  // 内部调用
}
// main.c
extern void internal_func();  // 错误!无法访问static函数
extern void public_func();    // 可以调用

3.  extern关键字的作用

        简单来说就是跨文件访问,声明变量或函数是在其他文件中定义的,以便在当前文件中使用。

3.1  声明外部全局变量

        告诉编译器某个变量或函数在别的文件中定义,链接时再去查找。

// file1.c
int global_var = 42;  // 定义全局变量

// file2.c
extern int global_var;  // 声明变量在外部定义

void print_var() {
    printf("%d\n", global_var);  // 可以访问file1.c的global_var
}

        如果不加 extern,编译器会认为 global_var 是新的变量,导致链接错误。

3.2  声明外部函数

        作用同上。

3.3  注意事项

① 不能初始化(否则变成定义,而不是声明)

extern int x = 10;  // 错误!这是定义,不是声明
extern int y;       // 正确,只是声明

② 滥用 extern 可能导致变量被意外修改,建议配合 static 或模块化设计使用。

3.4  extern vs static

特性externstatic
作用声明变量/函数在外部定义限制变量/函数作用域
存储类别不影响存储位置静态存储区(非栈/堆)
链接性外部链接(跨文件可见)内部链接(仅当前文件可见)
典型用途跨文件共享变量/函数隐藏实现、持久化变量

4.  const关键字的作用

4.1  定义变量为常量

        局部变量或者全局变量可以通过const来定义为常量,一旦赋值后,改常量的值就不能被修改。

const int MAX_SPEED = 100;  // 定义常量,不可修改

//MAX_SPEED = 200;  // 错误!不能修改const变量

//const int MAX_SPEED // 错误:常量在定义时必须初始化

4.2  修饰函数的参数

        使用const修饰函数参数,表示该参数在函数体内不能被修改,这样可以保证函数不会无意间修改传入的参数值,增加代码的可维护:

void func(const int x)
{
    // x = 10; //错误:x是常量,不能被修改
}

4.3  修饰指针

4.3.1  指针指向的数据不可变

const int *ptr = &x;  // ptr可以变,但*ptr不能变
*ptr = 10;  // 错误!不能修改*ptr
ptr = &y;   // 合法,可以改变ptr指向的地址

4.3.2  指针本身不可变(常量指针)

int *const ptr = &x;  // ptr不能变,但*ptr可以变
*ptr = 10;  // 合法
ptr = &y;   // 错误!不能修改ptr

4.3.3  指针和指向的数据都不可变

const int *const ptr = &x;  // ptr和*ptr都不能变
*ptr = 10;  // 错误!
ptr = &y;   // 错误!

4.4  在嵌入式开发中的特殊用途

4.4.1  定义只读硬件寄存器

const volatile uint32_t * const UART_TX_REG = (uint32_t *)0x40001000;

const 表示寄存器是只读的(防止误写)。

volatile 告诉编译器不要优化访问(每次都要读取)。

4.4.2  节省Flash存储(常量数据放ROM)

const uint8_t LOOKUP_TABLE[] = {0x01, 0x02, 0x03};  // 存储在Flash而非RAM

4.4.3   提高代码安全性(防止意外修改)

void send_data(const uint8_t *data, uint32_t len) {
    // data指向的内容不能被修改
    for (uint32_t i = 0; i < len; i++) {
        UART_SEND(data[i]);  // 只能读取,不能写
    }
}

千题千解·嵌入式工程师八股文详解_时光の尘的博客-CSDN博客

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐