-
2021-01-27 10:11:44
volatile关键字是一种类型修饰符,用它声明的变量类型可以被某些编译器未知的因素更改,比如:操作系统、硬件或其他线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对该特殊地址的稳定访问。声明volatile语法如下:
int volatile vInt;
,当要求使用volatile声明的变量的值时,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。例如:volatile int i=10; int a = i; ... // 其他代码,并未明确告诉编译器,对 i 进行过操作 int b = i;
volatile关键字指出i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读取数据的代码之间没有对i进行过操作,它会自动把上次读的数据放在b中,而不是重新从i里面读。
更多相关内容 -
c语言volatile关键字的作用是什么?
2021-08-26 00:10:35但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化。 二.volatile详解: 1.原理作用: volatile的本意是“易变的” ,因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取...一.前言
1.编译器优化介绍:
由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬件重新排序引起的问题的解决办法是在从硬件(或者其他处理器)的角度看必须以特定顺序执行的操作之间设置内存屏障(memory barrier),linux 提供了一个宏解决编译器的执行顺序问题。
void Barrier(void)
这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。
2.volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以消除一些代码。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化。
二.volatile详解:
1.原理作用:
volatile的本意是“易变的” ,因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。
当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。
精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化(都会直接从变量内存地址中读取数据),从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)
2.看两个事例:
1>告诉compiler不能做任何优化
比如要往某一地址送两指令:
int *ip =...; //设备地址
*ip = 1; //第一个指令
*ip = 2; //第二个指令
以上程序compiler可能做优化而成:
int *ip = ...;
*ip = 2;
结果第一个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:
volatile int *ip = ...;
*ip = 1;
*ip = 2;
即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。
2>用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份。
3.下面是使用volatile变量的几个场景:
1>中断服务程序中修改的供其它程序检测的变量需要加volatile;
2>多任务环境下各任务间共享的标志应该加volatile
3>存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。
例如:
假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000。
int *output = (unsigned int *)0xff800000;//定义一个IO端口;
int init(void)
{
int i;
for(i=0;i< 10;i ){
*output = i;
}
}
经过编译器优化后,编译器认为前面循环半天都是废话,对最后的结果毫无影响,因为最终只是将output这个指针赋值为9,所以编译器最后给你编译编译的代码结果相当于:
int init(void)
{
*output = 9;
}
如果你对此外部设备进行初始化的过程是必须是像上面代码一样顺序的对其赋值,显然优化过程并不能达到目的。反之如果你不是对此端口反复写操作,而是反复读操作,其结果是一样的,编译器在优化后,也许你的代码对此地址的读操作只做了一次。然而从代码角度看是没有任何问题的。这时候就该使用volatile通知编译器这个变量是一个不稳定的,在遇到此变量时候不要优化。
例如:
volatile int *output=(volatile unsigned int *)0xff800000;//定义一个I/O端口
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中禁止任务调度,3中则只能依靠硬件的良好设计。
4.几个问题
1)一个参数既可以是const还可以是volatile吗?
可以的,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2) 一个指针可以是volatile 吗?
可以,当一个中服务子程序修该一个指向一个buffer的指针时。
5.volatile的本质:
1> 编译器的优化
在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致。
当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致。
当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致。
2>volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人。声明:
本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。
-
C语言volatile关键字理解
2020-07-18 19:26:51如 果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。 编译器优化介绍 内存访问速度远不及CPU处理速度,为提高机器整体性能,...volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如 果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
编译器优化介绍
内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬件重新排序引起的问题的解决办法是在从硬件(或者其他处理器)的角度看必须以特定顺序执行的操作之间设置内存屏障(memory barrier),linux 提供了一个宏解决编译器的执行顺序问题。——void Barrier(void)函数
这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。C语言关键字volatile(注意它是用来修饰变量而不是上面介绍的__volatile__)表明某个变量的值可能在外部被改变,因此对这些变量的存取不能缓存到寄存器,每次使用时需要重新存取。
C 编译器没有线程概念
如下程序
该线程启动时将intSignal 置为2,然后循环等待直到intSignal 为1 时退出。显然intSignal的值必须在外部被改变,否则该线程不会退出。但是实际运行的时候该线程却不会退出,即使在外部将它的值改为1。
对于C编译器来说,它并不知道这个值会被其他线程修改。自然就把它cache在寄存器里面。这时候就需要用到volatile。volatile 的本意是指:这个值可能会在当前线程外部被改变。也就是说,我们要在threadFunc中的intSignal前面加上volatile关键字,这时候,编译器知道该变量的值会在外部改变,因此每次访问该变量时会重新读取。
-
C语言volatile关键字详解
2019-10-31 16:13:581.volatile和什么有关 百度翻译是这样子翻译volatile的: 图1-1 百度翻译volatile截图 ...volatile属于C语言的关键字,《C Primer Puls》 是这样解释关键字的:关键字是C语言的词汇,由于编译器...1.volatile和什么有关
百度翻译是这样子翻译volatile的:
图1-1 百度翻译volatile截图
volatile属于C语言的关键字,《C Primer Puls》 是这样解释关键字的:关键字是C语言的词汇,由于编译器不具备真正的智能,所以你必须用编译器能理解的术语表示你的意图。开发者告诉编译器该变量是易变的,无非就是希望编译器去注意该变量的状态,时刻注意该变量是易变的,每次读取该变量的值都重新从内存中读取。(ahhhh,是不是一脸蒙蔽,举个例子吧)
int i = 10; int main(void){ int a, b; a = i; ...//伪代码,里面不含有对 a 、 b 以及 i的操作 b = i; if(a == b){ printf("a = b"); } else { printf("a != b"); } return 0; }
如上代码,如果选择编译器优化,可能会被编译成如下代码(当然不是在C语言层面上优化,而是在汇编过程优化,只是使用C程序举例):
int i = 10; int main(void){ int a, b; a = i; ...//伪代码,里面不含有对 a 、 b 以及 i的操作 b = i; printf("a = b"); return 0; }
因为在仅仅从main主函数来看,a == b是必然的,那么在什么情况,a 和 b不是必然相等呢?
1. i 是其他子线程与主线程共享的全局变量,其他子线程有可能修改 i 值;
2. i 是中断函数与主函数共享的全局变量,中断函数有可能修改 i 值;
3. i 属于硬件寄存器,CPU可能通过硬件直接改变 i 的值(例如寄存器的标志位)
但是仔细想一想,好像我们都遇到过上述情况,也没有对相对应的变量使用volatile修饰呀?也没出现奇怪的问题呀?本小白猜测,大佬您是不是没有开启编译器优化,编译器其实是默认不优化的,这对入门者是友好的,但是当进入企业开发中,我们可能就会遇到 leader 在编译源码时,选择了编译器优化,以减少可执行程序大小和提高性能,这时候我们就不得不去考虑编译器优化问题,如何启动编译器优化,我们结合 GCC 编译器和 keil 开发软件讲解。
使用GCC编译器时,在编译脚本命令加入 -On ; n: 0 ~ 3,数字代表优化等级,数字越大,优化级别越高。
例如:
gcc -O2 -O hello hello.c
使用 O2 优化级别编译 hello.c
使用keil 软件,我们可以通过如下操作选择优化级别:
2.volatile关键字什么情况下要用
此博文为了限幅,达到更好的阅读效果,仅仅对如下几个方面进行简单分析,如需更加深入了解,可以访问实验博文进行查看。
2.1自定义延时函数
#include <stdio.h> void delay(long val); int main(){ delay(1000000); return 0; } void delay(long val){ while(val--); }
相信大佬们对如上程序都挺熟悉的,特别是玩过单片机的同学,主要是通过CPU不断进行无意义的操作达到延时的效果,这种操作如果不启用编译器优化是可以达到预期效果的,但是启用编译器优化就会被优化成如下效果(当然不是在C语言层面上优化,而是在汇编过程优化,只是使用C程序举例):
#include <stdio.h> void delay(long val); int main(){ delay(1000000); return 0; } void delay(long val){ ; }
这个时候,delay函数就起不了效果了,需要使用 volatile 修饰 val ;具体可见:
编译器优化对自定义延时程序的影响(volatile详解实验一)
2.2多线程共享的全局变量
多线程数据安全问题一直是计算机领域十分常见的问题,为了解决这类问题,衍生出互斥锁、条件变量、临界区以及自旋锁等解决办法,如上都是为了线程数据同步,但是要做到线程数据同步,我们还需要注意一个编译器优化问题。
我们都知道,每一个线程虽然共享一个进程的资源,但是每个线程同样拥有自己的私有堆栈,保证每个线程函数中定义的局部变量相互之间不可见;线程间通信是十分简单的,其中一个十分常见的方式就是通过共享全局变量,全局变量对于每一个线程都是可见的,但是线程的每一次读写全局变量都是对全局变量直接操作吗,答案是否定的。
例如下面这个操作(伪代码):
//一个全局变量a int a = 1; int main(){ int b,c,d,e,f; //多次赋值 b = a; c = a; d = a; e = a; f = a; .... } void *child_pth_fun{ //子线程修改a值 a = 2; ...... }
如果每次赋值都去内存中读入 a , 对于程序来说开销实在太大了,这时候编译器优化会引入一个中间变量,加快程序执行效率,也正是因为优化原因,如果这个全局变量是多线程共享的,子线程可能在任意时刻改变a的值,但是主程序引入的中间变量值确实过去a的值,就可能出现数据未同步问题。
会出现什么问题、怎么解决此类问题、怎么去复现数据不同步问题、想看看博主有多傻逼 都看看
编译器优化对多线程数据同步的影响(volatile详解实验二)
2.3中断函数与主函数共享的全局变量
中断函数和主函数共享的全局变量需要使用 volatile 修饰的情况是相似的。大家可以感受实验二,去做一个中断的实验。(对于只学过stm32,没有接触linux的同学可以在下面评论,博主按照需求去开实验三)
编译器优化对中断数据同步的影响(volatile详解实验三)2.4硬件寄存器
什么叫硬件寄存器,学过硬件的同学应该不陌生,我们在做按键检测的时候是不是下面这种流程:
1.设置GPIO对应的寄存器配置成输入模式
2.不断地去访问GPIO电平标志寄存器(或者是一个寄存器的标志位)
3.根据寄存器值的某个二进制位确定当前引脚电平
那么有没有想过一个问题,是什么去改变硬件寄存器的值?其实,硬件寄存器上的值的是和底层电路相关的,硬件寄存器的值会影响电路,电路也会反过来影响硬件寄存器的值。
所以在这种情况下,编译器更不应该拷贝副本,而应该每次读写都从内存中读写,保证数据正确,声明成volatile可以防止出现数据出错问题。
例如:
//GPIOE13 ---->LEDD7 //GPIOA28 ----> KEY2 //注意:裸机程序是直接在硬件上运行的程序,是不能使用标准C库。 #define GPIOEALTFN0 (*(volatile unsigned int *)0xC001E020) #define GPIOEOUTENB (*(volatile unsigned int *)0xC001E004) #define GPIOEOUT (*(volatile unsigned int *)0xC001E000) #define GPIOAALTFN1 (*(volatile unsigned int *)0xC001A024) #define GPIOAOUTENB (*(volatile unsigned int *)0xC001A004) #define GPIOAPAD (*(volatile unsigned int *)0xC001A018) void _start(void) //gcc编译器中,裸机程序的入口是start,不是main { GPIOEALTFN0 &= ~(3<<26); GPIOEOUTENB |= (1<<13); GPIOAALTFN1 &= ~(3<<24); GPIOAOUTENB &= ~(1<<28); while(1) { //读取GPIO引脚电平 if(!(GPIOAPAD & (1<<28))) GPIOEOUT &= ~(1<<13); else GPIOEOUT |= (1<<13); } }
这种情况加volatile的情况是最多的,比如stm32函数库底层的寄存器定义就是加了volatile的:
所以,也没有实验ahhh!
编译器优化对硬件寄存器数值的影响(volatile详解实验四) -
关于C语言中volatile关键字
2013-08-20 06:30:20精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词... -
C语言应用笔记(四):C语言volatile关键字及其使用
2021-05-26 05:25:01在 C 语言中,还有一个并不常用但却非常有用的关键字 volatile。volatile 总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量... -
嵌入式开发-c语言中volatile关键字作用
2014-04-08 20:45:12一个定义为volatile 的变量是说这变量可能会被意想不到地改变,这样,编 译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必 须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里... -
c语言volatile关键字在单片机中的作用
2018-04-01 21:21:25volatile 的意思是“易失的,易改变的”。这个限定词的含义是向编译器指明变量的内容可能会由于其他程序的修改而变化。通常在程序中申明了一个变量时,编译器会尽量把它存放在通用寄存器中,例如ebx。当CPU把其值放... -
C语言volatile关键字的作用
2019-04-06 20:14:36但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化。 二、volatile详解: 1.volatile的本意是"易变得"因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少内存的优化,但... -
C语言:volatile关键字
2021-07-29 23:17:50C语言之volatile关键字 -
C语言中volatile关键字
2021-08-29 20:38:46volatile关键字是C语言中非常冷门的关键字,因为用到这个关键字的场景并不多。 当不用这个关键字的时候,CPU可能会对我们的代码做一定的优化: 内存中的数据要放入CPU中进行运算或控制,而这个数据的值是被放入... -
C语言 volatile关键字的使用
2020-09-23 13:49:57volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。 volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。 作用 简单地说就是... -
C语言 volatile 关键字在编译优化过程中有何作用
2021-05-18 11:38:56今天讲述的话题就是关于代码优化中,关键字volatile在优化过程中起到的作用。一、关键字volatile是什么?volatile是一个类型修饰符(type specifier)。volatile的作用是作为指令关键字,确保本条指令不会因编译器的... -
C语言关键字详解(五)带你全面了解 volatile 关键字
2022-03-25 21:44:37大家好,欢迎来到C语言深度解析专栏—C语言关键字详解第五篇,在本篇中我们将会介绍C语言当中的另外两个重要的关键字 volatile 和 extern ,相信大家在看完这篇博客后会对 volatile 和 extern 这两个关键字的用法及... -
C语言中volatile关键字的使用
2021-05-18 09:34:36描述volatile的意思是易变的、可变的,作用是限制编译器优化某些变量。首先看一段C51程序:Keil在优化级别是为8时得到如下汇编代码(部分未列出):可以看到,变量d的值赋给x,y,z时,只有x中是直接读取的d中数值,而... -
C语言中volatile关键字用法
2021-03-06 18:10:52介绍Volatile关键字之前我们先了解一下编译器的优化。 高速缓存:处理器读取程序里面的数据时,把一些访问频率比较高的数据,临时存储到寄存器(register)中,当需要取数据时,就会从 register 中取,而不是直接去从... -
C语言——volatile关键字讲解
2021-10-10 23:45:22许多程序员都无法正确理解C语言关键字volatile,这并不奇怪。因为大多数C语言书籍通常都是一两句一带而过,本文将告诉你如何正确使用它。 在C/C++嵌入式代码中,你是否经历过以下情况: 代码执行正常–直到你打开了... -
C语言volatile关键字在单片机中的作用
2019-05-18 13:04:55在C语言中,这个限定词的含义是向编译器指明变量的内容可能会由于其它程序的修改而变化。 通常,在程序中申明了一个变量时,编译器会尽量把它放在通用寄存器中,例如ebx。当CPU把其值放到ebx中后就不会再关心对应... -
C++中volatile关键字及常见的误解总结
2020-08-27 10:25:38主要给大家介绍了关于C++中volatile关键字及常见的误解的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
C语言中volatile关键字的作用
2020-06-18 08:51:03在看C语言基础知识的时候看到了volatile关键字,不是很理解,所以查了资料,总结一下。 volatile译为:易变的 volatile是一个类型修饰符(type specifier),就像我们熟悉的const一样,它是被设计用来修饰被不同线程... -
C语言 volatile 关键字
2017-10-14 01:02:50如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。下面举例说明。在DSP开发中,经常需要等待某个事件的触发,所以经常会写...