Chinaunix首页 | 论坛 | 博客
  • 博客访问: 864031
  • 博文数量: 149
  • 博客积分: 3671
  • 博客等级: 中校
  • 技术积分: 1701
  • 用 户 组: 普通用户
  • 注册时间: 2010-06-03 16:52
文章分类

全部博文(149)

文章存档

2011年(57)

2010年(92)

分类: C/C++

2010-08-25 10:02:50

一、volatile变量

       C/C++ 中的 volatile 关键字和 const 对应,用来修饰变量,通常用于建立语言级别的 。这是 BS 在 "The C++ Programming Language" 对 volatile 修饰词的说明:

        A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.

        volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。用volatile关键字声明的变量i每一次被访问时,执行部件都会从i相应的内存单元中取出i的值。

        没有用volatile关键字声明的变量i在被访问的时候可能直接从cpu的寄存器中取 值(因为之前i被访问过,也就是说之前就从内存中取出i的值保存到某个寄存器中),之所以直接从寄存器中取值,而不去内存中取值,是因为编译器优化代码的 结果(访问cpu寄存器比访问ram快的多)。

        以上两种情况的区别在于被编译成汇编代码之后,两者是不一样的。之所以这样做是因为变量i可能会经常变化,保证对特殊地址的稳定访问。

        使用该关键字的例子如:int volatile nVint;
        当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。 例如:
             volatile int i=10;
       int a = i;
       ...
       //其他代码,并未明确告诉编译器,对i进行过操作
       int b = i;
        volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
        注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
        首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:C++语言:

01 include <stdio.h>
02
03     void main( )
04     {
05            int i=10;
06        int a = i;
07        printf("i= %d\n",a);
08       //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
09        __asm {
10            mov dword ptr [ebp-4], 20h
11        }
12        int b = i;
13        printf("i= %d\n",b);
14     }

/*其实不只是“内嵌汇编操纵栈”这种方式属于编译器无法识别的变量改变,另外更多的可能是多线程并发访问共享变量时,一个线程改变了变量的值,怎样让改变后的值对其它线程 visible。*/

        然后,在调试版本模式运行程序,输出结果如下:
         i = 10
      i = 32
  然后,在release版本模式运行程序,输出结果如下:
      i = 10
      i = 10
  输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。
  下面,我们把 i的声明加上volatile关键字,看看有什么变化:
  #i nclude
  void main()
  {
      volatile int i=10;
      int a = i;
      printf("i= %d\n",a);
      __asm {
          mov dword ptr [ebp-4], 20h
      }
      int b = i;
      printf("i= %d\n",b);
  }
  分别在调试版本和release版本运行程序,输出都是:
      i = 10
      i = 32
  这说明这个关键字发挥了它的作用!vc的截图如下:
    备注
        系统总是在 volatile 对象被请求的那一刻读取其当前值,即使上一条指令从同一对象请求值。而且,该对象的值在赋值时立即写入。
        volatile 修饰符通常用于由多个线程访问而不使用 lock 语句来序列化访问的字段。使用 volatile 修饰符能够确保一个线程检索由另一线程写入的最新值。

二、典型应用举例

1.  
volatilefor ( int i=0; i<100000; i++);
        这个语句用来测试空循环的速度的 ,但是编译器肯定要把它优化掉,根本就不执行
如果你写成:  
for ( volatile int i=0; i<100000; i++);
        它就会执行了。

        volatile的本意是“易变的” ,volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人,“易变”是因为外在因素引起的,像多线程,中断等, 并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化。
2.   
        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也应该如此说明。

3.
        一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必 须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
            1). 并行设备的硬件寄存器(如:状态寄存器)
            2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
            3). 多线程应用中被几个任务共享的变量
        嵌入式系统程序员常用到volatile,嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求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;
             }

4.
        volatile一般说来,volatile用在如下的几个地方:

    1)
中断服务程序中修改的供其它程序检测的变量需要加volatile;
    2) 多任务环境下各任务间共享的标志应该加volatile;
    3) 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

    另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。

三、volatile指针



四、VC6.0中的Debug模式和Release模式区别
   
     Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。

        debug跟release在初始化变量时所做的操作是不同的,debug是将每个字节位都赋成0xcc, 而release的赋值近 似于随机(我想是直接从内存中分配的,没有初始化过)。这样就明确了,如果你的程序中的某个变量没被初始化就被引用,就很有可能出现异常:用作控制变量将 导致流程导向不一致;用作数组下标将会使程序崩溃;更加可能是造成其他变量的不准确而引起其他的错误。所以在声明变量后马上对其初始化一个默认的值是最简 单有效的办法,否则项目大了你找都没地方找。代码存在错误在debug方式下可能会忽略而不被察觉到,如debug方式下数组越界也大多不会出错,在 release中就暴露出来了,这个找起来就比较难了:( 。

        Debug版本就是调试版本,Visual C++ 6.0默认的就是Debug版本。在Debug版本中,可以使用单步执行、跟踪等功能,但其生成的可执行文件比较大,代码运行比较慢。Release版本 就是发行版本,其运行速度较快,可执行文件较小,但在其编译条件下无法执行调试功能。  

        还有一点,Release版本的exe文件链接的目标是标准的MFC DLL(Use MFC in a shared or static dll)。比如MFC42.DLL。这些DLL在安装windows的时候,就会装到系统中。因此,这样的exe在没有安装Visual C++ 6.0的机器上也能运行。而Debug版本的exe链接了调试版本的MFC DLL文件,比如MFC42.DLL。在没有安装Visual C++ 6.0的机器上不能运行,因为缺少MFC42D.DLL等,除非选择use static dll when link。   

        Debug版本中包含大量的调试信息,所以我们能够单步执行、Watch表达式等等,而release版本仅包含我们的代码。由于要利于程序的测 试,Debug版本的程序附带很多测试信息和测试程序时才需要的代码,所以Debug版本的程序需要VC的Debug(注意这里不是指程序的Debug, 而是指VC的调试器)才能运行。而Release版本就不具有这些特性,所以在Release版本的程序上不能做调试!打包就相当于将你制作的东西发布出 去,应该是优化过的代码,当然要用发布版本,即Release版本。

        两者所用的动态连接库是不一样的,Release版本所需要的dll和lib已经包含在Windows的system(或者system32)下,所以只 需要拷贝就可以运行了,但是Debug版本需要的dll和lib是在安装vc时装上去的,如果你想直接将debug版本给用户,需要拷贝几个文件,但这样 显得很臃肿,一般来说不可取。

         Debug 和 Release 的真正区别,在于一组编译选项。

Debug 版本参数含义  
/MDd /MLd 或 /MTd 使用 Debug runtime library(调试版本的运行时刻函数库)  
/Od 关闭优化开关  
/D "_DEBUG" 相当于 #define _DEBUG,打开编译调试代码开关(主要针对assert函数)  
/ZI  
创建 Edit and continue(编辑继续)数据库,这样在调试过程中如果修改了源代码不需重新编译  
GZ 可以帮助捕获内存错误

Release 版本 参数含义  
/MD /ML 或 /MT 使用发布版本的运行时刻函数库  
/O1 或 /O2 优化开关,使程序最小或最快  
/D "NDEBUG" 关闭条件编译调试代码开关(即不编译assert函数)  
/GF 合并重复的字符串,并将字符串常量放到只读内存,防止被修改  

        Debug 和 Release 并没有本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项行动。
参考: http://www.cnblogs.com/jembai/archive/2009/01/13/1374805.html

五、

       C++优化器为每个文件生成目标代码,链接程序把各个模块连接起来,解释每个模块的外部引用,并链 入一些系统库,最终生成可执行程序。为了检查源码语法是否正确,编译器要执行静态类型检查,但程序中使用的外部变量和函数编译器无从知晓,需要程序员在使 用前声明。声明告诉编译器这个名字会在某处定义,它应该按声明的这样使用,而定义才会分配内存,定义同时也有声明的作用。链接程序只会链接含有你使用的函 数或变量的模块。

阅读(4939) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~