-
2021-05-18 16:57:03
C语言volatile与const同时使用应注意的问题
const最主要的特点就是只读,有常量、常量指针,如果不是特别小心的分析C语言语句的书写格式,再加上指针的使用,就特别容易弄错。volatile关键字是一个类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,下面就有小编分享C语言volatile与const同时使用应注意的问题,欢迎大家学习!
const和volatile放在一起的意义在于:
(1)本程序段中不能对a作修改,任何修改都是非法的,或者至少是粗心,编译器应该报错,防止这种粗心;
(2)另一个程序段则完全有可能修改,因此编译器最好不要做太激进的优化。
“const”含义是“请做为常量使用”,而并非“放心吧,那肯定是个常量”。
“volatile”的含义是“请不要做没谱的'优化,这个值可能变掉的”,而并非“你可以修改这个值”。
因此,它们本来就不是矛盾的。
const修饰的变量不允许这里修改不代表不允许别处修改,比如:
复制代码 代码如下:
int i = 5;
const int* p = &i;
*p = 6; // 不可以;
i = 7; // 完全可以,而且那个“const”的“*p”也跟着变成了7。
对于非指针非引用的变量,const volatile同时修饰的意义确实不大。个人觉得。
需要明白“volatile”的含义并非是“non-const”。所以他们才可以放在一起。
在C++语言中,const没有反义词,如果一个变量没有const修饰,那它本身就是const的反义词,而并非加上volatile才是const的反义词。
两者同时修饰一个对象的典型情况,是用于驱动中访问外部设备的只读寄存器。
留一个问题:const volatile int i=10;这行代码有没有问题?如果没有,那 i 到底是什么 属性?
回答一:没有问题,例如只读的状态寄存器。它是volatile,因为它可能被意想不到地改变;它是const,因为程序不应该试图去修改它。volatile和const并不矛盾,只是控制的范围不一样,一个在程序本身之外,另一个是程序本身。
回答二:没问题,const和volatile这两个类型限定符不矛盾。const表示(运行时)常量语义:被const修饰的对象在所在的作用域无法进行修改操作,编译器对于试图直接修改const对象的表达式会产生编译错误。volatile表示“易变的”,即在运行期对象可能在当前程序上下文的控制流以外被修改(例如多线程中被其它线程修改;对象所在的存储器可能被多个硬件设备随机修改等情况):被volatile修饰的对象,编译器不会对这个对象的操作进行优化。一个对象可以同时被const和volatile修饰,表明这个对象体现常量语义,但同时可能被当前对象所在程序上下文意外的情况修改。另外,LS错误,const可以修饰左值,修饰的对象本身也可以作为左值(例如数组)。
更多相关内容 -
C语言 volatile与const同时使用应注意的问题
2021-01-01 01:49:12const和volatile放在一起的意义在于: (1)本程序段中不能对a作修改,任何修改都是非法的,或者至少是粗心,编译器应该报错,防止这种粗心;(2)另一个程序段则完全有可能修改,因此编译器最好不要做太激进的优化... -
C语言volatile的应用举例
2021-11-22 17:54:54volatile 本意为“易变的”,它的作用是,当程序以一种编译器不知道的方式更改变量时,防止编译器对其进行任何优化(需要注意的是编译器的优化等级)。用volatile定义的变量会在程序外被改变,每次都必须从内存中读取...volatile 本意为“易变的”,它的作用是,当程序以一种编译器不知道的方式更改变量时,防止编译器对其进行任何优化(需要注意的是编译器的优化等级)。用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份。
volatile变量的例子:1、中断的发生,
1.char flag = 0; 2. 3.int main(void) 4.{ 5. ... 6. while(1) 7. { 8. if (flag) 9. do_something(); 10. } 11.} 12. 13./*中断服务程序*/ 14.void ISR_1(void) 15.{ 16. flag = 1; 17.}
上述程序存在的一个问题就是 flag 没有使用 volatile 进行修饰,而编译器判断在 main 函数里没有别的操作对 flag 的值进行更改,可能就只执行一次从 flag 到某寄存器的读取操作,这时中断里对 flag 的操作将被忽略,函数将一直执行不到 do_something 的内容。用 volatile 将可以避免这个问题的出现。
2、对寄存器的操作
在嵌入式开发中,常常要操作寄存器,对寄存器进行写入,读出等等操作。每个寄存器都有自己固有的地址,通过C语言访问这些地址就变得尤为重要。
#define GSTAT (*(volatile unsigned int *)0x560000B0)
在这里,我们举一个例子。这是一个状态寄存器的宏定义。首先,通过unsigned int我们能够知道,该寄存器是32位的。因为要避免程序执行过程中直接从cache中读取数据,所以用volatile进行修饰。
每次都要重新读取该地址上的值。首先(volatile unsigned int*)是一个指针,我们就假设它为p吧。它存储的地址就是后面的0x560000B0,然后取这个地址的值,也就是p,所以源代码变成了((volatile unsigned int *)0x560000B0),接下来我们就能直接赋值给GSTAT来改变地址0x560000B0上存储的值了。 -
单片机C语言中volatile的作用
2020-07-25 21:22:42本文给大家介绍了单片机C语言中volatile的作用。 -
c语言volatile关键字的作用是什么?
2021-08-26 00:10:35(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错) 2.看两个事例: 1>告诉compiler不能做任何优化 比如要...一.前言
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关键字详解
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的作用与使用场景
2019-08-07 17:30:57今天完成公司的任务,突然想起来在调试过程中遇到了一个问题是这样的:“我在...后面我咨询了一位大哥,然后他告诉我在变量i前面加上一个volatile。果然,代码运行和我的预期一样了”。代码如下 void main() { ... -
C语言volatile关键字理解
2020-07-18 19:26:51volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如 果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用... -
C语言volatile的用法
2018-08-28 14:27:53volatile修饰的变量表明是随时改变的,程序执行时不要进行编译优化,既每次都从变量的地址中读取数据(作用与const相反),不要读取寄存器中的备份。因为访问寄存器的速度要快过SRAM(内存),编译器一般都会做减少存取... -
深入讲解C语言编程中volatile修饰符的作用
2020-09-03 04:53:48主要介绍了C语言编程中volatile修饰符的作用,文章深入到内存优化方面进行解析,非常推荐!需要的朋友可以参考下 -
c语言volatile和static关键字
2019-06-24 09:21:28volatile关键字:https://blog.csdn.net/tigerjibo/article/details/7427366、 static关键字:https://blog.csdn.net/t595180928/article/details/80448249 -
C语言volatile类型限定符详解
2021-09-24 19:43:05一、volatile类型限定符 volatile是一个类型修饰符(type specifier),就像我们熟悉的const一样,它是被设计用来修饰被不同线程访问和修改的变量;volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化... -
C语言volatile关键字在单片机中的作用
2019-05-18 13:04:55在C语言中,这个限定词的含义是向编译器指明变量的内容可能会由于其它程序的修改而变化。 通常,在程序中申明了一个变量时,编译器会尽量把它放在通用寄存器中,例如ebx。当CPU把其值放到ebx中后就不会再关心对应... -
c语言volatile关键字在单片机中的作用
2019-08-30 15:09:11c语言volatile关键字在单片机中的作用 volatile 的意思是“易失的,易改变的”。这个限定词的含义是向编译器指明变量的内容可能会由于其他程序的修改而变化。通常在程序中申明了一个变量时,编译器会尽量把它存放... -
C语言应用笔记(四):C语言volatile关键字及其使用
2021-05-26 05:25:01在 C 语言中,还有一个并不常用但却非常有用的关键字 volatile。volatile 总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量... -
C语言volatile
2016-03-24 21:37:13volatile 影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会 进行编译优化,加volatile关键字的变量... -
C语言 volatile 修饰 变量作用 总结
2016-07-16 22:20:09volatile 影响编译器编译的结果,指出,volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关... -
关于C语言中volatile关键字
2013-08-20 06:30:20volatile详解 一、volatile的本意是“易变的” 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的... -
C语言 volatile关键字的使用
2020-09-23 13:49:57volatile是一个类型修饰符(type specifier),就像我们熟悉的const一样,它是被设计用来修饰被不同线程访问和修改的变量;volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接... -
C语言 volatile
2020-04-08 12:13:57关键字volatile是什么?如何使用? (1)英文意思: volatile:易变的; (2)定义 确保本条指令不会因编译器的优化而省略,且要求每次直接存取原始内存地址的值 (2)为什么有用? 需要涉及到,编译器,cpu里面的... -
C语言volatile关键字
2019-05-30 18:56:50volatile 的意思是“易失的,易改变的”。这个限定词的含义是向编译器指明变量的内容可能会由于其他程序的修改而变化。通常在程序中申明了一个变量时,编译器会尽量把它存放在通用寄存器中,例如ebx。当CPU把其值放... -
C语言volatile用法,(没阅读,对错未知)
2017-04-24 16:19:59volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用... -
【转载】C语言volatile
2019-08-27 16:02:002017年10月20日 12:00:10 小帅哥gaoyi 阅读数 274 文章标签: volatile 更多 分类专栏: C 版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:... -
C语言中volatile的用法
2013-03-20 15:37:06C语言中volatile的用法