阅读 67

volatile关键字的作用面试,volatile关键字是什么

一.前言1 .编译器优化介绍:

由于内存访问速度远远不及CPU的处理速度,为了提高机器整体性能,在硬件上引入了硬件缓存Cache,以加快内存的访问速度。 另外,在现代的CPU中,命令的执行并不一定严格按照顺序执行,为了最大限度地利用CPU的命令流水线来提高执行速度,可以按顺序执行没有关联性的命令。 以上就是硬件水平的优化。 让我们来看看软件级别的优化。 一个是程序员在编写代码时进行优化,另一个是编译器进行优化。 编译器优化的常用方法是将内存变量缓存在寄存器中; 使用CPU指令流水线来调整指令的顺序。 常见的情况是更改读写命令的顺序。 在优化常规内存时,这些优化是透明和高效的。 编译器优化或硬件排序问题的解决方案是在从硬件(或其他处理器)的角度看必须以特定顺序执行的操作之间提供内存屏障。 linux提供了解决编译器执行顺序问题的宏。

这是语音栏(语音)

此函数通知编译器插入内存屏障,但对硬件无效。 编译的代码将当前CPU寄存器中所有更改的值存储在内存中,并在需要这些数据时从内存中重新读取。

2.volatile总是与优化相关联。 编译器中有一种叫做数据流分析的技术。 解析器中的变量在哪里代入,在哪里使用,在哪里无效? 分析结果可用于优化常数耦合、常数传播等,还可以消除一些代码。 但是,程序可能不需要这些优化。 在这种情况下,可以使用volatile关键字禁止这些优化。

1.volatile详细信息:1.volatile的原意是“易变”。 由于存取寄存器比存取存储单元快得多,所以编译器一般会进行减少存取存储器的优化,但有可能读取脏数据。 如果需要使用volatile声明变量的值,则即使上一条指令刚从其中读取数据,系统也会始终从其内存中读取数据。 确切地说,当遇到使用该关键字声明的变量时,编译器将不再优化访问该变量的代码,从而提供对特殊地址的稳定访问。如果不使用valatile,编译器将优化声明的语句。 (简而言之,volatile关键字会影响编译器的编译结果,而用volatile声明的变量表示该变量随时可能发生变化。 有关该变量的运算,请不要为了没有错误而进行编译优化。)

2 .看两个案例:

告诉计算机无法进行优化

例如,要向一个地址发送两个命令:

int *ip=; //设备地址

*ip=1; //第一个命令

*ip=2;//第二个命令

上述程序compiler可能已经过优化。

int *ip=;

*ip=2;

结果的第一个命令丢失。 使用volatile时,compiler不允许优化,并保证程序的原意。

volatile int *ip=;

*ip=1;

*ip=2;

尝试优化compiler不会使两个价值语句之间保持一致。 只能进行其他优化。

在volatile中定义的变量在程序外更改,每次都必须从内存中读取,不能重用放入cache或寄存器的备份。

例如:

volatile char a;

a=0;

while (! a ) {

//do some things;

}

doother ();

如果没有volatiledoother (),则不执行

以下是一些使用volatile变量的场景:

1由中断服务程序更改的其他程序检测到的变量需要添加volatile;

例如:

静态输入I=0;

入主(void ) )。

{

.

wile(1) {

if(I ) dosomething );

}

}

/* Interrupt service routine. */

voidISR_2(void ) )

{

i=1;

}

程序的本意是在发生ISR_2中断时在main函数中调用dosomething函数,但由于编译器确定在main函数中I没有发生更改,因此只需执行一次从I读取到某个寄存器的操作

每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。


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应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人。

6.下面的函数有什么错误:

int square(volatile int *ptr)

{

return *ptr * *ptr;

}

该程序的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)

{

int a,b;

a = *ptr;

b = *ptr;

return a * b;

}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)

{

int a;

a = *ptr;

return a * a;

}

注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐