c语言volatile关键字(详细)总结附示例讲解

news/2023/6/7 23:32:13

目录

    • 一、简介
    • 二、示例代码解析
      • 2.1 修饰变量
      • 2.2 修饰硬件寄存器地址
    • 三、其他相关链接

一、简介

volatile属于C语言的关键字。开发者告诉编译器该变量是易变的,无非就是希望编译器去注意该变量的状态,时刻注意该变量是易变的,让编译器不再去优化被volatile修饰的变量的操作,每次读取该变量的值都重新从内存中读取,但是volatile并不能做内存屏障的功能,想使用内存屏障请使用平台相关的屏障指令,比如GCC提供了一个内联asm volatile (“” : : : “memory”);的编译器屏障。
一般说来,volatile用在如下的几个地方:

1、中断服务程序中修改的供其它程序检测的变量需要加 volatile;
2、多任务环境下各任务间共享的标志应该加 volatile;
3、存储器映射的硬件寄存器通常也要加 volatile 说明,因为每次对它的读写都可能由不同意义;

二、示例代码解析

2.1 修饰变量

一个简单的打印变量程序:

#include <stdio.h>void main()
{int i = 10;int a = i;printf("i = %d", a);
}

程序输出如下:

i = 10

下面采用volatile关键字修饰:

#include <stdio.h>void main()
{int i = 10;int a = i;printf("i = %d", a);//下面汇编语句的作用就是改变内存中 i 的值
//但是又不让编译器知道__asm {mov dword ptr [ebp-4], 20h}int b = i;printf("i = %d", b);
}

volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。
然后,在 Debug 版本模式运行程序,输出结果如下:

i = 10
i = 32

然后,在 Release 版本模式运行程序,输出结果如下:

i = 10
i = 10

输出的结果明显表明,Release 模式下,编译器对代码进行了优化,第二次没有输出正确的 i 值。下面,我们把 i 的声明加上 volatile 关键字,看看有什么变化:

#include <stdio.h>void main()
{volatile int i = 10;int a = i;printf("i = %d", a);__asm {mov dword ptr [ebp-4], 20h}int b = i;printf("i = %d", b);
}

分别在 Debug 和 Release 版本运行程序,输出都是:

i = 10
i = 32

这说明这个 volatile 关键字发挥了它的作用。其实不只是内嵌汇编操纵栈"这种方式属于编译无法识别的变量改变,另外更多的可能是多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程 visible。

2.2 修饰硬件寄存器地址

1、寄存器地址的定义:

#define UART_BASE_ADRS (0x10000000)     /* 串口的基地址 */
#define UART_RHR *(volatile unsigned char *)(UART_BASE_ADRS + 0)  /* 数据接受寄存器 */
#define UART_THR *(volatile unsigned char *)(UART_BASE_ADRS + 0)  /* 数据发送寄存器 */

2、寄存器读写操作:

UART_THR = ch; /* 发送数据 */
ch = UART_RHR; /* 接收数据 */

也可采用定义带参数宏实现

#define WRITE_REG(addr, ch) *(volatile unsigned char *)(addr) = ch
#define READ_REG(addr, ch) ch = *(volatile unsigned char *)(addr)

3、对寄存器相应位的操作方法:

定义寄存器

#define UART_LCR *(volatile unsigned char *)(UART_BASE_ADRS + 3)  /* 线控制寄存器 */

采用标准C的强制转换和指针的概念来实现访问MCU的寄存器,例如:

#define DDRB (*(volatile unsigned char *)0x25)

分析如下:
(unsigned char *)0x25中的0x25只是个值,前面加(unsigned char *)表示把这个值强制类型转换为unsigned char型的指针。再在前面加”*”,对这个0x25这个地址存放的值进行操作,然后#define相当于将0x25这个地址存放的值即*p指定为DDRB变量,这样当读/写以0x25为地址的寄存器存放的值时,直接操作DDRB即可,如下:

DDRB = 0xff;

相当于:

unsigned char *p, i;
p = 0x25;
i = *p;        //把地址为0x25单元中的数据读出送入i变量
*p = 0xff;     //向地址为0x25的单元中写入0xff

三、其他相关链接

1、C语言常用函数详细总结

2、C语言中指针、数组作为作为函数参数使用总结

3、C语言常见数据类型字节数和打印格式总结

4、C语言、Makefile和shell中添加打印调试信息总结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.exyb.cn/news/show-4561310.html

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

VMware ESXi中的虚拟机启用鼠标侧键/其他功能键

前提&#xff1a;需要有ESXi的操作权限 如果是VMware Workstation的话&#xff0c;启用鼠标功能键&#xff0c;需要对.vmx配置文件进行操作。而ESXi的虚拟机配置文件是不可以直接访问的。 首先将虚拟机关机&#xff0c;再在ESXi的web控制台中启用ssh管理 启用后&#xff0c;使…

鼠标侧键BetterTouchTool过期、设置问题解决

文章目录过期设置过期 问题&#xff1a; 如果试用期到了&#xff0c;说明是开了代理导致的。 解决&#xff1a; 用App Cleaner卸载干净&#xff0c;重新安装。 设置 问题&#xff1a; 遇到虚拟机无法侧键切屏&#xff0c;或者侧键切屏非100%有效。 解决&#xff1a; 设置为…

设计模式_行为型模式 -《责任链模式》

设计模式_行为型模式 -《责任链模式》 笔记整理自 黑马程序员Java设计模式详解&#xff0c; 23种Java设计模式&#xff08;图解框架源码分析实战&#xff09; 概述 在现实生活中&#xff0c;常常会出现这样的事例&#xff1a;一个请求有多个对象可以处理&#xff0c;但每个对象…

无线鼠标侧键功能被篡改

2019独角兽企业重金招聘Python工程师标准>>> 周末在家看小黄片,用的迅雷影音播放器; 播放之前,播放器自动下载了一个解码器,xxx.dll,下载很快,解码器的名字一闪而过,我也没记住; 然后开始播放; 播了一会儿,暂停,到资源管理器找一个文件; 资源管理器有"前进&quo…

vue3+vite 动态侧边栏+面包屑+当前栈的访问路由

效果图 在本示例管理系统中&#xff0c;由于每个用户的权限不一样&#xff0c;拥有的可以访问的路由页面也不一样。用户能访问的路由页面都是后端根据权限动态配置的。我们前端需要根据后端接口返回的路由表去动态增删路由&#xff0c;从而生成这个用户所拥有的路由。 permi…

secureCRT压缩文件夹并将其传至本地

要将一个目录下的所有文件或者文件夹下载到本地&#xff0c;需要将文件夹压缩成一个文件&#xff0c;再传至本地 压缩文件夹 打包&#xff08;压缩&#xff09;&#xff1a;tar -czvf fileName.tar dirName&#xff0c;将文件夹dirName及其子文件打包为fileName.tar; 更多请参…

数控刀具国家标准

2006-04-19 08:26:30 数控刀具国家标准 一&#xff0e;可转位刀具刀片型号编制标准 &#xff11;&#xff0e;可转位车刀型号表示规则   GB&#xff0f;T5343.1&#xff0c;它等效采用ISO5680&#xff0d;1989。它适用于可转位外圆车刀、端面车刀、防形车刀及拼装复合刀具的模…