• C中volatile总结


            在CPU处理过程中,需要将内存中的数据载入到寄存器中才能计算,所以可能涉及到一个问题,如果内存中的数据被更改了,但是寄存器还是使用的旧数据,这样就会造成数据的不同步。

    一、volatile关键字的作用

            使用volatile关键字定义变量,就是告诉编译系统这个变量可能会被意想不到的被改变。编译器就不会对变量进行代码优化。编译器在编译代码时,优化器每次遇到这个变量,都会从内存中重新读取内容,而不会使用保存在寄存器里的备份内容。

    二、使用volatile的场景

    • 在中断服务程序中修改的,供其它程序检测的变量(非auto),通常需要定义为volatile     

            中断服务可能会频繁进入,当变量被加载到寄存器中,马上就要被使用时,这时又来了一个中断修改了内存中的变量,如果不加volatile,被使用的变量就是寄存器中保存的也即修改之前的。

    • 在多任务环境下,各任务间共享的标志,通常也需要定义为volatile

            这个情形同中断,可能会使数据不同步。

    • 存储器映射的硬件寄存器通常也需要定义为volatile,因为每次对它的读写都可能有不同意义

            这个情形也类似两种,存储器的数据被转移到了硬件寄存器,这时存储器的数据被更改了,但是程序还可能使用的是硬件寄存器中的数据,这也是数据不同步。

            在stm32中,内存被映射到各种外设上,外设有自己的寄存器组,比如GPIO寄存器组

    1. typedef struct
    2. {
    3. __IO uint32_t CRL;
    4. __IO uint32_t CRH;
    5. __IO uint32_t IDR;
    6. __IO uint32_t ODR;
    7. __IO uint32_t BSRR;
    8. __IO uint32_t BRR;
    9. __IO uint32_t LCKR;
    10. } GPIO_TypeDef;
    11. #define __IO volatile

    可以看到寄存器都使用了__IO进行修饰,而__IO就是根据volatile定义的一个宏。

    三、案例

    1、逻辑分析仪

    在使用keil 5分析变量的波形时,变量循环从1->0->1,但是波形一直是处理于低,没有起伏。

    1. uint32_t flag1;
    2. void delay( uint32_t count )
    3. {
    4. for (; count!=0; count--);
    5. }
    6. int main(void) {
    7. while(1) {
    8. flag1 = 1;
    9. delay( 1000 );
    10. flag1 = 0;
    11. delay( 1000 );
    12. }
    13. }

    分析结果如下所示: 

    在flag1用volatile修饰之后波形如下所示 :

    2、 硬件寄存器

    在直接操作寄存器进行输出时,比如引脚拉到了LED上,LED另一端接高电平,引脚输出0是会点亮,当ODR不使用volatile修饰时,下面的操作编译器优化之后可能就只有 

    GPIOB->ODR = 0x00000001 这一句代码了,那么灯是不会亮的,但是实际上灯会闪烁的,因为ODR就是用volatile修饰的。

    1. GPIOB->ODR = 0x00000001;
    2. delay(100);
    3. GPIOB->ODR = 0x00000000;
    4. delay(100);
    5. GPIOB->ODR = 0x00000001;

    四、面试

    volatile 常见的几个面试题

    1、一个参数既可以是const还可以是volatile吗?

            可以,针对的角度不同可以这样理解

    const 告诉程序员 这是一个常量,不要更改它,在尝试更改时,编译器会报错

    volatile告诉编译器,不要对变量做任何优化,直接从内存中读取内容。

    2、一个指针可以是volatile 吗?

      可以,指针和普通变量一样,有时也有变化程序的不可控性,比如一个中服务子程序修改一个指向buffer的指针时,即从一个buffer指向另一个buffer,如果不加volatile,面临的问题如同 二、使用volatile的场景 中的一样。

    3、下面的函数有什么错误?

    1. int square(volatile int*ptr)
    2. {
    3. return*ptr * *ptr;
    4. }

    该程序的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

    1. int square(volatile int*ptr) {
    2. int a,b;
    3. a = *ptr;
    4. b = *ptr;
    5. return a * b;
    6. }

    由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

    1. long square(volatile int*ptr) {
    2. int a = *ptr;
    3. return a * a;
    4. }

    - 注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile

  • 相关阅读:
    工程中的SOVD——从ECU到车辆
    模拟实现memcpy和memmove
    珠江水运杂志珠江水运杂志社珠江水运编辑部2022年第17期目录
    零数科技向海南省委书记汇报数字金融创新
    Linux 之搭建 arm 的 qemu 模拟器
    新版开源UI千月影视APP源码/后端基于ThinkPHP框架/前后端完美匹配
    django-vue系统报错解决记录
    现在的编程语言越来越多,为什么 C 和 C++ 还没有被现在的时代淘汰呢?
    element-plus 设置 el-date-picker 弹出框位置
    【电子通识】DC头中的5525和5521有什么区别
  • 原文地址:https://blog.csdn.net/TSC1235/article/details/133604096