IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言中关键词volatile的用法(二) -> 正文阅读

[C++知识库]C语言中关键词volatile的用法(二)

volatile用于声明变量时的使用的限定符。它告诉编译器该变量值可能随时发生变化,且这种变化并不是代码引起的。给编译器这个暗示是很重要的。
volatile的声明:
1.声明一个变量为volatile,可以在数据类型之前或之后加上关键字volatile。下面的语句,把变量abc声明一个volatile的整型变量;
volatile int abc;
int volatile abc;
2.把指针指向的变量声明为volatile很常见,尤其是I/O寄存器的地址映射。下面的语句,把pReg声明为一个指向8-bit无符号指针,指针指向的内容为volatile。
volatile uint8_t * pReg;
uint8_t volatile * pReg;
3.volatile的指针指向非volatile的变量很少见(我只使用过一次),但我还是给出相应的语法。
int * volatile p;
4.最后,如果你再struct或者union前使用volatile关键字,表明struct或者union的所有内容都是volatile。当然也可以在struct或者union成员上使用volatile关键字。

volatile的使用方法:
只要变量可能被意外的修改,就需要把该变量声明为volatile。在实际应用中,只有三种类型数据可能被修改:
1.外设寄存器地址映射;
2.在中断服务程序中修改全局变量;
3.在多线程、多任务应用中,全局变量被多个任务读写。

外设寄存器
嵌入式系统包含真正的硬件,通常会有复杂的外设。这些外设寄存器的值可能被异步的修改。举个简单的例子,我们要把一个8-bit状态寄存器的地址映射到0x1234。在程序中循环查看该状态寄存器的值是否变为非0。
下面是最容易想到,但错误的实现方法:
uint8_t * pReg = (uint8_t *)0x1234;
while(*pReg == 0) {printf("found *pReg == 0");}
当你打开编译器优化时,程序总是执行失败。因为编译器会生成下面的汇编代码:
mov ptr, #0x1234

mov a, @ptr
loop:
bz loop
程序被优化的原因很简单,既然已经把变量的值读入累加器,就没有必要重新一遍,编译器认为值是不会变化的。就这样,在第三行,程序进入了无限死循环。为了告诉编译器我们的真正意图,我们需要修改函数的声明:
uint8_t volatile * pReg = (uint8_T volatile *)0x1234
编译器生成的汇编代码:
mov ptr, #0x1234

loop:
mov a, @ptr
bz loop

中断服务程序
在中断服务程序中,经常会修改一些全局变量值,来作为主程序中的判断条件。例如,在串口中断服务程序中,可能会检测是否接收到了ETX(假如是消息的结束标识符)字符。如果接收到了ETX,ISR设置一个全局标志位。
错误的做法:

int etx_rcvd = FALSE;
void main()
{
  ...
  while(!etx_rvcd)
  {
    //wait
  }
  ...
interrupt void rx_isr(void)
{
  ...
  if(ETX == rx_char)
  {
    etx = TRUE;
  }
  ...
}
}

在关闭编译器优化的情况下,程序可能执行正常。然而,任何像样点而优化都会“break”这段程序。问题是编译器并不知道etx_rcvd可能被ISR中被修改。编译器只知道,表达式!ext_rcvd始终为真,你讲用于无法退出循环。结果,循环后面的代码可能被编译器优化掉。
幸运的话,你的编译器可能会发出警告;不幸的话,(或者你不认真的查看编译器警告),你的程序无法正常执行。当然,你可以责怪编译器执行了“糟糕的优化”。
解决方式是,将变量etx_rcvd声明为volatile,所有问题(当然,也可能是部分问题)就消失了。

多线程应用
在实时系统中,尽管有想queues,pipes等这些同步机制,使用全局变量实现两个任务共享信息的做法依然很常见。即使在你的程序中加入了抢占式调度器,你的编译器依然无法知道什么是上下文切换,或何时发生上下文切换。因此从概念上讲,多任务修改全局变量的的做法与中断服务程序中修改全局变量的做法是相同的。因此,所有这类全局变量都应该声明为volatile。

在多线程、多任务应用中,全局变量被多个任务读写
在C中,有volatile关键字,它的作用就是在多线程时保证变量的内存可见性,但是具体怎么理解呢?
比如对于一个四核三级缓存的CPU,它的缓存结构是这样的。

我们可以看到L3是四个核共有的,但是L2,L1其实是每个核私有的,如果我有一个变量var,它会被两个线程同时读取,这两个线程在两个核上并行执行,因为我们的缓存原理,这个var可能分别在两个核的 L2或L1缓存,这样读取速度最快,但是该var值可能就分别被这两个核分别修改成不同的值, 最后将值回写到L3或L4主存,此时就会发生bug了。
所以volatile关键字就是预防这种情况,对于被volatile修饰的的变量,每次CPU需要读取时,都至少要从L3读取,并且CPU计算结束后,也立刻回写到L3中,这样读写速度虽然减慢了一些,但是避免了该值在每个core的私有缓存中单独操作而其他核不知道。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-16 18:39:12  更:2021-11-16 18:40:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 6:31:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码