从volatile看代码优化
volatile的作用: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值.
简单地说就是防止编译器对代码进行优化.比如如下程序:
XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;
如果对外部硬件上述四条语句分别表示不同的操作,会产生四种不同的动作,那么编译器就不能像对待纯粹的程序那样对上述四条语句进行优化只认为XBYTE[2]=0x58;而忽略前三条语句(即只产生一条机器代码),此时编译器会逐一的进行编译并产生相应的机器代码(四条).
volatile变量的几个例子
推荐一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是真正懂得volatile完全的重要性。
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。
3). 这段代码是个恶作剧。这段代码的目的是用来返指针*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;
}
关键在于两个地方:
1. 编译器的优化 (请高手帮我看看下面的理解)
在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;
当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致
当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致
当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
典型的例子
for ( int i=0; i<100000; i );
这个语句用来测试空循环的速度的
但是编译器肯定要把它优化掉,根本就不执行
如果你写成
for ( volatile int i=0; i<100000; i );
它就会执行了
volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
static int i=0;
int main(void)
{
...
while (1)
{
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此
可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被
调用。如果将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
/******************************以上来自百度百科***********************/
考虑这volatile的优化问题,是因为编写的程序不能正常工作,代码截取如下:
- #define GPBCON (*(volatile unsigned long *)0xa0000010)
- #define GPBDAT (*(volatile unsigned long *)0xa0000014)
- #define GPB5_out (1<<(5*2))
- #define GPB6_out (1<<(6*2))
- #define GPB7_out (1<<(7*2))
- #define GPB8_out (1<<(8*2))
- static inline int wait(void)
- {
- unsigned int dly = 50000;
- while(dly--);
- }
- int main(void)
- {
- unsigned int i = 32;
- GPBCON = GPB5_out|GPB7_out|GPB8_out|GPB6_out;
- GPBDAT = 0xffffffff;
- while(1)
- {
- GPBDAT = ~i;
- i<<=1;
- if(i==512)
- i=32;
- wait();
- }
- return 0;
- }
只是实现控制流水灯的小程序。但却无法正常运行,原因在于被优化了,看反汇编代码如下:
- b0004000 <main>:
- b0004000: e3a0320a mov r3, #-1610612736 ; 0xa0000000
- b0004004: e3a02b55 mov r2, #87040 ; 0x15400
- b0004008: e3e01000 mvn r1, #0 ; 0x0
- b000400c: e5832010 str r2, [r3, #16]
- b0004010: e5831014 str r1, [r3, #20]
- b0004014: e3a02020 mov r2, #32 ; 0x20
- b0004018: e1a01003 mov r1, r3
- b000401c: e1e03002 mvn r3, r2
- b0004020: e1a02082 lsl r2, r2, #1
- b0004024: e3520c02 cmp r2, #512 ; 0x200
- b0004028: e5813014 str r3, [r1, #20]
- b000402c: 03a02020 moveq r2, #32 ; 0x20
- b0004030: eafffff9 b b000401c <main+0x1c>
- b0004034: 43434700 movtmi r4, #14080 ; 0x3700
- b0004038: 5328203a .word 0x5328203a
- b000403c: 6372756f .word 0x6372756f
- b0004040: 20797265 .word 0x20797265
- b0004044: 202b2b47 .word 0x202b2b47
- b0004048: 6574694c .word 0x6574694c
- b000404c: 30303220 .word 0x30303220
- b0004050: 2d317139 .word 0x2d317139
- b0004054: 29363731 .word 0x29363731
- b0004058: 332e3420 .word 0x332e3420
- b000405c: 4100332e .word 0x4100332e
- b0004060: 00000029 .word 0x00000029
- b0004064: 62616561 .word 0x62616561
- b0004068: 1f010069 .word 0x1f010069
- b000406c: 05000000 .word 0x05000000
- b0004070: 06005434 .word 0x06005434
- b0004074: 09010802 .word 0x09010802
- b0004078: 14041201 .word 0x14041201
- b000407c: 17011501 .word 0x17011501
- b0004080: 19011803 .word 0x19011803
- b0004084: 1e021a01 .word 0x1e021a01
- b0004088: Address 0xb0004088 is out of bounds.
从中可以看出没有进入过wait函数,因为编译时使用了-O2选项,编译器把这自定义的函数wait给删了。
如何使编译器不删除这自定义函数呢?使用volatile
把函数wait替换成
static inline int wait(void)
{
volatile unsigned int dly = 50000;
while(dly--);
}
这样编译后看看反汇编:
- b0004000 <main>:
- b0004000: e3a0320a mov r3, #-1610612736 ; 0xa0000000
- b0004004: e3a02b55 mov r2, #87040 ; 0x15400
- b0004008: e3e01000 mvn r1, #0 ; 0x0
- b000400c: e5832010 str r2, [r3, #16]
- b0004010: e5831014 str r1, [r3, #20]
- b0004014: e3a01cc3 mov r1, #49920 ; 0xc300
- b0004018: e24dd008 sub sp, sp, #8 ; 0x8
- b000401c: e1a00003 mov r0, r3
- b0004020: e2811050 add r1, r1, #80 ; 0x50
- b0004024: e3a02020 mov r2, #32 ; 0x20
- b0004028: e1e03002 mvn r3, r2
- b000402c: e1a02082 lsl r2, r2, #1
- b0004030: e5803014 str r3, [r0, #20]
- b0004034: e3520c02 cmp r2, #512 ; 0x200
- b0004038: e58d1004 str r1, [sp, #4]
- b000403c: 03a02020 moveq r2, #32 ; 0x20
- b0004040: e59d3004 ldr r3, [sp, #4]
- b0004044: e3530000 cmp r3, #0 ; 0x0
- b0004048: e2433001 sub r3, r3, #1 ; 0x1
- b000404c: e58d3004 str r3, [sp, #4]
- b0004050: 1afffffa bne b0004040 <main+0x40>
- b0004054: eafffff3 b b0004028 <main+0x28>
- b0004058: 43434700 movtmi r4, #14080 ; 0x3700
- b000405c: 5328203a .word 0x5328203a
- b0004060: 6372756f .word 0x6372756f
- b0004064: 20797265 .word 0x20797265
- b0004068: 202b2b47 .word 0x202b2b47
- b000406c: 6574694c .word 0x6574694c
- b0004070: 30303220 .word 0x30303220
- b0004074: 2d317139 .word 0x2d317139
- b0004078: 29363731 .word 0x29363731
- b000407c: 332e3420 .word 0x332e3420
- b0004080: 4100332e .word 0x4100332e
- b0004084: 00000029 .word 0x00000029
- b0004088: 62616561 .word 0x62616561
- b000408c: 1f010069 .word 0x1f010069
- b0004090: 05000000 .word 0x05000000
- b0004094: 06005434 .word 0x06005434
- b0004098: 09010802 .word 0x09010802
- b000409c: 14041201 .word 0x14041201
- b00040a0: 17011501 .word 0x17011501
- b00040a4: 19011803 .word 0x19011803
- b00040a8: 1e021a01 .word 0x1e021a01
- b00040ac: Address 0xb00040ac is out of bounds.
看到了吧,第20行的数字开始减一操作,就是wait函数
LAY
阅读(1396) | 评论(0) | 转发(0) |