Chinaunix首页 | 论坛 | 博客
  • 博客访问: 165165
  • 博文数量: 56
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 14
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-24 17:36
文章分类

全部博文(56)

文章存档

2016年(43)

2015年(9)

2014年(2)

2013年(2)

我的朋友

分类: C/C++

2016-06-01 18:14:10

原文地址:从volatile看代码优化 作者:暴风lay

                        从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的优化问题,是因为编写的程序不能正常工作,代码截取如下:

 

点击(此处)折叠或打开

  1. #define GPBCON (*(volatile unsigned long *)0xa0000010)
  2. #define GPBDAT (*(volatile unsigned long *)0xa0000014)

  3. #define GPB5_out (1<<(5*2))
  4. #define GPB6_out (1<<(6*2))
  5. #define GPB7_out (1<<(7*2))
  6. #define GPB8_out (1<<(8*2))

  7. static inline int wait(void)
  8. {    
  9.     unsigned int dly = 50000;
  10.     while(dly--);
  11. }
  12. int main(void)
  13. {
  14.     unsigned int i = 32;
  15.     GPBCON = GPB5_out|GPB7_out|GPB8_out|GPB6_out;
  16.     GPBDAT = 0xffffffff;
  17.     while(1)
  18.     {
  19.         GPBDAT = ~i;
  20.         i<<=1;
  21.         if(i==512)
  22.             i=32;
  23.         wait();
  24.     }
  25.     return 0;
  26. }

只是实现控制流水灯的小程序。但却无法正常运行,原因在于被优化了,看反汇编代码如下:

点击(此处)折叠或打开

  1. b0004000 <main>:
  2. b0004000:    e3a0320a     mov    r3, #-1610612736    ; 0xa0000000
  3. b0004004:    e3a02b55     mov    r2, #87040    ; 0x15400
  4. b0004008:    e3e01000     mvn    r1, #0    ; 0x0
  5. b000400c:    e5832010     str    r2, [r3, #16]
  6. b0004010:    e5831014     str    r1, [r3, #20]
  7. b0004014:    e3a02020     mov    r2, #32    ; 0x20
  8. b0004018:    e1a01003     mov    r1, r3
  9. b000401c:    e1e03002     mvn    r3, r2
  10. b0004020:    e1a02082     lsl    r2, r2, #1
  11. b0004024:    e3520c02     cmp    r2, #512    ; 0x200
  12. b0004028:    e5813014     str    r3, [r1, #20]
  13. b000402c:    03a02020     moveq    r2, #32    ; 0x20
  14. b0004030:    eafffff9     b    b000401c <main+0x1c>
  15. b0004034:    43434700     movtmi    r4, #14080    ; 0x3700
  16. b0004038:    5328203a     .word    0x5328203a
  17. b000403c:    6372756f     .word    0x6372756f
  18. b0004040:    20797265     .word    0x20797265
  19. b0004044:    202b2b47     .word    0x202b2b47
  20. b0004048:    6574694c     .word    0x6574694c
  21. b000404c:    30303220     .word    0x30303220
  22. b0004050:    2d317139     .word    0x2d317139
  23. b0004054:    29363731     .word    0x29363731
  24. b0004058:    332e3420     .word    0x332e3420
  25. b000405c:    4100332e     .word    0x4100332e
  26. b0004060:    00000029     .word    0x00000029
  27. b0004064:    62616561     .word    0x62616561
  28. b0004068:    1f010069     .word    0x1f010069
  29. b000406c:    05000000     .word    0x05000000
  30. b0004070:    06005434     .word    0x06005434
  31. b0004074:    09010802     .word    0x09010802
  32. b0004078:    14041201     .word    0x14041201
  33. b000407c:    17011501     .word    0x17011501
  34. b0004080:    19011803     .word    0x19011803
  35. b0004084:    1e021a01     .word    0x1e021a01
  36. b0004088:    Address 0xb0004088 is out of bounds.

从中可以看出没有进入过wait函数,因为编译时使用了-O2选项,编译器把这自定义的函数wait给删了。
如何使编译器不删除这自定义函数呢?使用volatile
把函数wait替换成
static inline int wait(void)

 volatile unsigned int dly = 50000;
 while(dly--);
}
这样编译后看看反汇编:

点击(此处)折叠或打开

  1. b0004000 <main>:
  2. b0004000:    e3a0320a     mov    r3, #-1610612736    ; 0xa0000000
  3. b0004004:    e3a02b55     mov    r2, #87040    ; 0x15400
  4. b0004008:    e3e01000     mvn    r1, #0    ; 0x0
  5. b000400c:    e5832010     str    r2, [r3, #16]
  6. b0004010:    e5831014     str    r1, [r3, #20]
  7. b0004014:    e3a01cc3     mov    r1, #49920    ; 0xc300
  8. b0004018:    e24dd008     sub    sp, sp, #8    ; 0x8
  9. b000401c:    e1a00003     mov    r0, r3
  10. b0004020:    e2811050     add    r1, r1, #80    ; 0x50
  11. b0004024:    e3a02020     mov    r2, #32    ; 0x20
  12. b0004028:    e1e03002     mvn    r3, r2
  13. b000402c:    e1a02082     lsl    r2, r2, #1
  14. b0004030:    e5803014     str    r3, [r0, #20]
  15. b0004034:    e3520c02     cmp    r2, #512    ; 0x200
  16. b0004038:    e58d1004     str    r1, [sp, #4]
  17. b000403c:    03a02020     moveq    r2, #32    ; 0x20
  18. b0004040:    e59d3004     ldr    r3, [sp, #4]
  19. b0004044:    e3530000     cmp    r3, #0    ; 0x0
  20. b0004048:    e2433001     sub    r3, r3, #1    ; 0x1
  21. b000404c:    e58d3004     str    r3, [sp, #4]
  22. b0004050:    1afffffa     bne    b0004040 <main+0x40>
  23. b0004054:    eafffff3     b    b0004028 <main+0x28>
  24. b0004058:    43434700     movtmi    r4, #14080    ; 0x3700
  25. b000405c:    5328203a     .word    0x5328203a
  26. b0004060:    6372756f     .word    0x6372756f
  27. b0004064:    20797265     .word    0x20797265
  28. b0004068:    202b2b47     .word    0x202b2b47
  29. b000406c:    6574694c     .word    0x6574694c
  30. b0004070:    30303220     .word    0x30303220
  31. b0004074:    2d317139     .word    0x2d317139
  32. b0004078:    29363731     .word    0x29363731
  33. b000407c:    332e3420     .word    0x332e3420
  34. b0004080:    4100332e     .word    0x4100332e
  35. b0004084:    00000029     .word    0x00000029
  36. b0004088:    62616561     .word    0x62616561
  37. b000408c:    1f010069     .word    0x1f010069
  38. b0004090:    05000000     .word    0x05000000
  39. b0004094:    06005434     .word    0x06005434
  40. b0004098:    09010802     .word    0x09010802
  41. b000409c:    14041201     .word    0x14041201
  42. b00040a0:    17011501     .word    0x17011501
  43. b00040a4:    19011803     .word    0x19011803
  44. b00040a8:    1e021a01     .word    0x1e021a01
  45. b00040ac:    Address 0xb00040ac is out of bounds.
看到了吧,第20行的数字开始减一操作,就是wait函数
 
                                                              LAY
阅读(1384) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~