您的位置 首页 设计

C言语的那些小秘密之volatile

volatile的重要性对于搞嵌入式的程序员来说是不言而喻的,对于volatile的了解程度常常被不少公司在招聘嵌入式编程人员面试的时候作为衡量一个应聘者是否合格的参考标准之一,为什么volati

  volatile的重要性关于搞嵌入式的程序员来说是显而易见的,关于volatile的了解程度常常被不少公司在招聘嵌入式编程人员面试的时分作为衡量一个应聘者是否合格的参阅规范之一,为什么volatile如此的重要呢?这是因为嵌入式的编程人员要常常同中止、底层硬件等打交道,而这些都用到volatile,所以说嵌入式程序员必需求把握好volatile的运用。

  其实就象读者所了解的const相同,volatile是一个类型润饰符。在开端解说volatile之前咱们先来解说下接下来要用到的一个函数,知道怎么运用该函数的读者能够越过该函数的解说部分。

  原型:int gettimeofday ( struct timeval * tv , struct timezone * tz );

  头文件:#include

  功用:获取当时时刻

  回来值:假如成功回来0,失利回来-1,过错代码存于errno中。

  gettimeofday()会把现在的时刻用tv所指的结构回来,当地时区的信息则放到tz所指的结构中。

  [cpp] view plaincopytimeval结构界说为:

  struct timeval{

  long tv_sec;

  long tv_usec;

  };

  timezone 结构界说为:

  struct timezone{

  int tz_minuteswest;

  int tz_dsttime;

  };

  先来说说timeval结构体,其间的tv_sec寄存的是秒,而tv_usec寄存的是微秒。其间的timezone成员变量咱们很少运用,在此简略的说说它在gettimeofday()函数中的效果是把当地时区的信息则放到tz所指的结构中,在其间tz_minuteswest变量里寄存的是和Greenwich 时刻差了多少分钟,tz_dsttime日光节省时刻的状况。咱们在此首要的是重视前一个成员变量timeval,后一个咱们在此不运用,所以运用gettimeofday()函数的时分咱们把有一个参数设定为NULL,下面先来看看一段简略的代码。

  [cpp] view plaincopy#include

  #include

  int main(int argc, char * argv[])

  {

  struct timeval start,end;

  gettimeofday( &start, NULL ); /*测验开始时刻*/

  double timeuse;

  int j;

  for(j=0;j<1000000;j++)

  ;

  gettimeofday( &end, NULL ); /*测验停止时刻*/

  timeuse = 1000000 * ( end.tv_sec – start.tv_sec ) + end.tv_sec – start.tv_sec ;

  timeuse /= 1000000;

  printf("运转时刻为:%f\n",timeuse);

  return 0;

  }

  运转成果为:

  [cpp] view plaincopyroot@ubuntu:/home# ./p

  运转时刻为:0.002736

  现在来简略的剖析下代码,经过end.tv_sec – start.tv_sec 咱们得到了停止时刻跟开始时刻以秒为单位的时刻距离,然后运用end.tv_sec – start.tv_sec 得到停止时刻跟开始时刻以奇妙为单位的时刻距离。因为时刻单位的原因,所以咱们在此关于( end.tv_sec – start.tv_sec ) 得到的成果乘以1000000转换为微秒进行核算,之后再运用timeuse /= 1000000;将其转换为秒。现在了解了怎么经过gettimeofday()函数来测验start到end代码之间的运转时刻,那么咱们现在接下来看看volatile润饰符。

  一般在代码中咱们为了避免一个变量介意想不到的状况下被改动,咱们会将变量界说为volatile,这然后就使得编译器就不会自作主张的去“动”这个变量的值了。精确点说便是每次在用到这个变量时有必要每次都从头从内存中直接读取这个变量的值,而不是运用保存在寄存器里的备份。

  在举例之前咱们先大约的说下Debug和Release 形式下编译方法的差异,Debug 一般称为调试版别,它包括调试信息,而且不作任何优化,便于程序员调试程序。Release 称为发布版别,它往往是进行了各种优化,使得程序在代码巨细和运转速度上都是最优的,以便用户很好地运用。大致的知道了Debug和Release的差异之后,咱们下面来看看一段代码。

  [cpp] view plaincopy#include

  void main()

  {

  int a=12;

  printf("a的值为:%d\n",a);

  __asm {mov dword ptr [ebp-4], 0h}

  int b = a;

  printf("b的值为:%d\n",b);

  }

  先剖析下上面的代码,咱们运用了一句__asm {mov dword ptr [ebp-4], 0h}来修正动量a在内存中的值,假如有对这句代码功用不清楚的读者能够参阅我之前的一篇《C言语的那些小秘密之仓库》,在此就不做过多的解说了。前面现已解说了Debug和Release 编译方法的差异,那么咱们现在来比照看下成果。注:运用vc6编译运转,如无特别阐明,均在linux环境下编译运转。读者自己在编译的时分别忘了挑选编译运转的形式。

  运用Debug形式的成果为:

  [cpp] view plaincopya的值为:12

  b的值为:0

  Press any key to continue

  运用Release形式的成果为:

  [cpp] view plaincopya的值为:12

  b的值为:12

  Press any key to continue

  看看上面的运转成果咱们发现在Release形式进行了优化之后b的值为了12,可是运用Debug形式的时分b的值为0。为什么会呈现这样的状况呢?咱们先不说答案,再来看看下面一段代码。注:运用vc6编译运转

  [cpp] view plaincopy#include

  void main()

  {

  int volatile a=12;

  printf("a的值为:%d\n",a);

  __asm {mov dword ptr [ebp-4], 0h}

  int b = a;

  printf("b的值为:%d\n",b);

  }

  运用Debug形式的成果为:

  [cpp] view plaincopya的值为:12

  b的值为:0

  Press any key to continue

  运用Release形式的成果为:

  [cpp] view plaincopya的值为:12

  b的值为:0

  Press any key to continue

  咱们发现这种状况下不论运用Debug形式仍是Release形式都是相同的成果。现在咱们就来剖析下,在此之前咱们先说了Debug和Release 形式下编译方法的差异。

  先剖析上一段代码,因为在Debug形式下咱们并没有对代码进行优化,所以关于在代码中每次运用a值得时分都是从它的内存地址直接读取的,所以在咱们运用了__asm {mov dword ptr [ebp-4], 0h}句子改动了a的值之后,接下来运用a值的时分从内存中直接读取,所以得到的是更新后的a值;可是当咱们在Release形式下运转的时分,发现b的值为a之前的值,而不是咱们更新后的a值,这是因为编译器在优化的进程中做了优化处理。编译器发现在对a赋值之后没有再次改动a的值,所以编译器把a的值备份在了一个寄存器中,在之后的操作中咱们再次运用a值的时分就直接操作这个寄存器,而不去读取a的内存地址,因为读取寄存器的速度要快于直接读取内存的速度。这就使得了读到的a值为之前的12。而不是更新后的0。

  第二段代码中咱们运用了一个volatile润饰符,这种状况下不论在什么形式下都得到的是更新后的a的值,因为volatile润饰符的效果便是告知编译器不要对它所润饰的变量进行任何的优化,每次取值都要直接从内存地址得到。从这儿咱们能够看出,关于咱们代码中的那些易变量,咱们最好运用volatile润饰,以此来得到每次对其进行更新后的值。为了加深下咱们的形象咱们再来看看下面一段代码。

  [cpp] view plaincopy#include

  #include

  int main(int argc, char * argv[])

  {

  struct timeval start,end;

  gettimeofday( &start, NULL ); /*测验开始时刻*/

  double timeuse;

  int j;

  for(j=0;j<10000000;j++)

  ;

  gettimeofday( &end, NULL ); /*测验停止时刻*/

  timeuse = 1000000 * ( end.tv_sec – start.tv_sec ) + end.tv_usec -start.tv_usec;

  timeuse /= 1000000;

  printf("运转时刻为:%f\n",timeuse);

  return 0;

  }

  与之前咱们测验时刻的代码相同,咱们仅仅增大了for()循环的次数。

  先来看看咱们不运用优化的成果:

  [cpp] view plaincopyroot@ubuntu:/home# gcc time.c -o p

  root@ubuntu:/home# ./p

  运转时刻为:0.028260

  运用了优化的运转成果:

  [cpp] view plaincopyroot@ubuntu:/home# gcc -o p time.c -O2

  root@ubuntu:/home# ./p

  运转时刻为:0.000001

  从成果明显能够看出距离如此之大,可是假如咱们在上面的代码中修正一下int j为int volatile j之后再来看看如下代码:

  [cpp] view plaincopy#include

  #include

  int main(int argc, char * argv[])

  {

  struct timeval start,end;

  gettimeofday( &start, NULL ); /*测验开始时刻*/

  double timeuse;

  int volatile j;

  for(j=0;j<10000000;j++)

  ;

  gettimeofday( &end, NULL ); /*测验停止时刻*/

  timeuse = 1000000 * ( end.tv_sec – start.tv_sec ) + end.tv_usec -start.tv_usec;

  timeuse /= 1000000;

  printf("运转时刻为:%f\n",timeuse);

  return 0;

  }

  先来看看咱们不运用优化的运转成果为:

  [cpp] view plaincopyroot@ubuntu:/home# gcc time.c -o p

  root@ubuntu:/home# ./p

  运转时刻为:0.027647

  运用了优化的运转成果为:

  [cpp] view plaincopyroot@ubuntu:/home# gcc -o p time.c -O2

  root@ubuntu:/home# ./p

  运转时刻为:0.027390

  咱们发现此时此刻不论是否运用优化句子运转,时刻几乎没有改动,仅仅有细小的差异,这细小的差异是因为核算机自身所导致的。所以咱们经过关于上面一个没有运用volatile和下面一个运用了volatile的比照成果可知,运用了volatile的变量在运用优化句子是for()循环并没有得到优化,因为for()循环履行的是一个空操作,那么一般状况下运用了优化句子使得这个for()循环被优化掉,底子就不履行。就比如编译器在编译的进程中将i的值设置为大于或许等于10000000的一个数,使得for()循环句子不会履行。可是因为咱们运用了volatile,使得编译器就不会自作主张的去动咱们的i值,所以循环体得到了履行。举这个比如的原因是要让读者紧记,假如咱们界说了volatile变量,那么它就不会被编译器所优化。

  当然volatile还有那些值得注意的当地呢?因为拜访寄存器的速度要快过直接拜访内存的速度,所以编译器一般都会作削减关于内存的拜访,可是假如将变量加上volatile润饰,则编译器确保对此变量的读写操作都不会被优化。这样说或许有些笼统了,再看看下面的代码,在此就扼要的写出几步了。

  main()

  {

  int i=o;

  while(i==0)

  {

  ……

  }

  }

  剖析以上代码,假如咱们没有在while循环体结构里边改动i的值,编译器在编译的进程中就会将i的值备份到一个寄存器中,每次履行判别句子时就从该寄存器取值,那么这将是一个死循环,可是假如咱们做如下的修正:

  main()

  {

  int volatile i=o;

  while(i==0)

  {

  ……

  }

  }

  咱们在i的前面加上了一个volatile,假定while()循环体里边履行的是跟上一个彻底相同的操作,可是这个时分就不能说是一个死循环了,因为编译器不会再对咱们的i值进行"备份"操作了,每次履行判别的时分都会直接从i的内存地址中读取,一旦其值发生改动就退出循环体。

  最终给出一点便是在实际运用中volatile的运用的场合大致有以下几点:

  1、中止服务程序中修正的供其它程序检测的变量需求加volatile;

  2、多使命环境下各使命间同享的标志应该加volatile;

  3、存储器映射的硬件寄存器一般也要加volatile阐明,因为每次对它的读写都或许有不同含义。

  关于volatile的解说咱们到此就完毕了。因为自己水平有限,博客中的不当或过错之处在所难免,殷切希望读者批评指正。一起也欢迎读者一起讨论相关的内容,假如愿意沟通的话请留下你名贵的定见。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/ziliao/sheji/116518.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部