容易爆炸的volatile

前几天测试空循环速度时发生了一些匪夷所思的事情,经过调查,我锁定了凶手:关键字volatile

编译器:VS2013
语言:C/C++

关于这个关键字,简单来说就是通过保证内存的可见性来防止编译器的过度优化。
这么专业的一句话说出来,我却更加的迷惑了:什么是内存的可见性?为什么要防止编译器的过度优化?
说起来,这其实是同一个问题: 编译器太“聪明”了

void test()
{
    int i = 0;
    i = 1;
    i = 2;
    i = 3;
    i = 4;
}

这样一个函数,有五句代码,但是编译器会“觉得”i = 4;所以前面的语句其实并没有转换成为机器码(仅release),这其实在一般的情形下并没有什么区别,还提高了效率。但是并不符合我们编写时的想法,所以加上volatile后编译器就老老实实生成多条指令了。

内存可见性:

  • 线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后再取变量值时,就直接从寄存器中取值;
  • 当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致
  • 当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致
  • 当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致

几个例子

也就是说,我们不加上这个关键字,可能会出现一些让我们费解的现象

  • 空循环速度
    void test_loop()
    {
      clock_t c1 = clock();
      for (int i = 0; i < 1000000000; i++);
      clock_t c2 = clock();
      for (volatile int i = 0; i < 1000000000; i++);
      clock_t c3 = clock();
      cout << c2 - c1 << endl << c3 - c2<<endl;
    }

在debug模式下,我们看一看输出:
图片说明
换成release再试一次:
图片说明
很明显在release下第一个循环被优化掉了,速度快到飞起~

  • 变量值被意外改变(其实是故意~)
    void test_ebp()
    {
      int i = 10;
      int a = i;
      printf("i=%d\n", a);
      __asm
      {
          mov dword ptr[ebp - 8], 10h//“偷偷改变i的值”
      }//VS2013下i的地址是ebp - 8,其他编译器可能不一致,如VC++6.0下应该是ebp - 4
      int b = i;
      printf("i=%d\n", b);
    }
    我们看看debug下的输出:
    图片说明
    再看看release:
    图片说明
    很明显读取i值是直接从寄存器里读取了,所以结果居然一致
    给i加上volatile果然结果就都一致了:
    图片说明
  • 多线程下volatile带来的不可思议的结果
    我们先看下面的函数:
    int square(volatile int* &p)
    {
      return  (*p)*(*p);
    }
    看起来是求一个变量平方的函数,但是偏偏是经过volatile修饰的,从而在编译器眼里这个函数是这样的:
    int square(volatile int* &p)
    {
      int a = *p;
      int b = *p;
      return a*b;
    }
    如果不在多线程环境里,无非是让编译器多忙了几句,但是一旦在多线程下,其他线程可能会在a,b取值之间改变了*p的值,从而得出错误的结果:
    int A = 100;
    volatile int *PA = &A;
    int square(volatile int* &p)
    {
      return  (*p)*(*p);
    }
    DWORD WINAPI test(LPVOID lpParamter)
    {
      int ans = square2(PA);
      printf("%s  %d\n","pthread 1:",ans);
      for (int i = 0; i < 4200000000; i++)
      {
          if (i*i == ans)
          {
              cout << "right" << endl;
              return 0;
          }
      }
      cout << "wrong" << endl;
      return 0;
    }
    DWORD WINAPI test2(LPVOID lpParamter)
    {
      int i = 0;
      while (1)
      {
          *PA = i++;
      }
      return 0;
    }
    void test_pthread()
    {
      HANDLE hThread2 = CreateThread(NULL, 0, test2, NULL, 0, NULL);
      HANDLE hThread = CreateThread(NULL, 0, test, NULL, 0, NULL);
      CloseHandle(hThread);
      CloseHandle(hThread2);
    }
    在test函数里会对函数的出来的结果进行检测,判断是不是一个数的平方(即使溢出也没事)
    不过我要说的是:即便是在一个线程里不断改变*P的值,出错率还是挺低的~
    图片说明
全部评论

相关推荐

牛客83700679...:简历抄别人的,然后再投,有反馈就是简历不行,没反馈就是学历不行,多投多改只要技术不差机会总会有的
点赞 评论 收藏
分享
避坑恶心到我了大家好,今天我想跟大家聊聊我在成都千子成智能科技有限公司(以下简称千子成)的求职经历,希望能给大家一些参考。千子成的母公司是“同创主悦”,主要经营各种产品,比如菜刀、POS机、电话卡等等。听起来是不是有点像地推销售公司?没错,就是那种类型的公司。我当时刚毕业,急需一份临时工作,所以在BOSS上看到了千子成的招聘信息。他们承诺无责底薪5000元,还包住宿,这吸引了我。面试的时候,HR也说了同样的话,感觉挺靠谱的。于是,我满怀期待地等待结果。结果出来后,我通过了面试,第二天就收到了试岗通知。试岗的内容就是地推销售,公司划定一个区域,然后你就得见人就问,问店铺、问路人,一直问到他们有意向为止。如果他们有兴趣,你就得摇同事帮忙推动,促进成交。说说一天的工作安排吧。工作时间是从早上8:30到晚上18:30。早上7点有人叫你起床,收拾后去公司,然后唱歌跳舞(销售公司都这样),7:55早课(类似宣誓),8:05同事间联系销售话术,8:15分享销售技巧,8:30经理训话。9:20左右从公司下市场,公交、地铁、自行车自费。到了市场大概10点左右,开始地推工作。中午吃饭时间大约是12:00,公司附近的路边盖饭面馆店自费AA,吃饭时间大约40分钟左右。吃完饭后继续地推工作,没有所谓的固定中午午休时间。下午6点下班后返回公司,不能直接下班,需要与同事交流话术,经理讲话洗脑。正常情况下9点下班。整个上班的一天中,早上到公司就是站着的,到晚上下班前都是站着。每天步数2万步以上。公司员工没有自己的工位,百来号人挤在一个20平方米的空间里听经理洗脑。白天就在市场上奔波,公司的投入成本几乎只有租金和工资,没有中央空调。早上2小时,晚上加班2小时,纯蒸桑拿。没有任何福利,节假日也没有3倍工资之类的。偶尔会有冲的酸梅汤和西瓜什么的。公司的晋升路径也很有意思:新人—组长—领队—主管—副经理—经理。要求是业绩和团队人数,类似传销模式,把人留下来。新人不能加微信、不能吐槽公司、不能有负面情绪、不能谈恋爱、不能说累。在公司没有任何坐的地方,不能依墙而坐。早上吃早饭在公司外面的安全通道,未到上班时间还会让你吃快些不能磨蹭。总之就是想榨干你。复试的时候,带你的师傅会给你营造一个钱多事少离家近的工作氛围,吹嘘工资有多高、还能吹自己毕业于好大学。然后让你早点来公司、无偿加班、抓住你可能不会走的心思进一步压榨你。总之,大家在找工作的时候一定要擦亮眼睛,避免踩坑!———来自网友
qq乃乃好喝到咩噗茶:不要做没有专业门槛的工作
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务