目录
- 一、简介
- 二、示例代码解析
- 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中添加打印调试信息总结