• 上一篇文章学习了C/C++中的指针与数组的区别,点击链接进行查看:【软件开发底层知识修炼】二十七 C/C++中的指针与数组是不同的
  • 本篇文章将学习volatile关键字在C/C++中的作用

文章目录

  • 1 实例代码分析
  • 2 问题分析
  • 3 解决方案
  • 4 拓展: const和volatile
  • 4 总结

1 实例代码分析

在讲解volatile关键字的作用之前,我们先来看一个代码的例子,代码如下:

  • main.c
#include <stdio.h>
#include <pthread.h>extern const int g_ready;int main()
{launch_device();while( g_ready == 0 ){sleep(1);printf("main()  : g_ready = %d\n", g_ready);}printf("main()  : g_ready = %d\n", g_ready);return 0;
}
  • device.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>int g_ready = 0;void* th_fn(void* args)
{sleep(5);g_ready = 1;printf("th_fn()  : g_ready = %d\n", g_ready);
}void launch_device()
{pthread_t tid = 0;pthread_create(&tid, NULL, th_fn, NULL);
}

上面的代码,是非常容易懂的,这里就不再多说。我们直接编译运行看看:

  • gcc -pthread main.c device.c -o test.out
  • ./test.out

运行结果如下:

这个结果对于我们来说其实挺容易懂的。就是main中的while循环先每隔一秒打印执行一次,然后5秒后th_fn函数开始执行,th_fn函数将g_ready变量变为1,然后打印一个语句。最后回到main中的while循环,由于while中上次最后一秒现在才执行完,打印一句,然后由于此时g_ready为1,所以退出循环,然后打印循环外的一个语句,然后结束程序的运行。这种结果,其实是比较容易理解的。也是比较普遍的结果。

但是如果我们再编译上述代码的时候,加上了-O3选项,该选项是优化选项,且级别比较高。编译运行结果如下:

  • gcc -O3 -pthread main.c device.c -o test.out
  • ./test.out

运行结果如下动态图:

由以上结果我们可以看到,程序陷入了死循环,g_ready没有被改变一直都是0,所以main中的while循环一直在执行。对于这个结果,可能并不是所有人都知道为什么。下面,我们就要来讲解为什么会产生上面的运行结果。

2 问题分析

-O3选项是让编译器对代码进行优化。

  • 编译优化时,编译器根据当前文件进行优化
  • 为了效率上的提高,优化时编译器将变量值从内存中读取进入寄存器
  • 每次要访问变量时直接从寄存器读取。毕竟寄存器的存取比内存的存取快很多。

那么,我们明白了优化对变量的影响。对于上述的代码,如果在编译时加了-O3选项。编译优化时,编译器会将变量g_ready的值放到寄存器中,以后每次使用该变量就从寄存器中取出g_ready的值使用即可。这样虽然是速度快了,但是当在th_fn函数中改变了g_ready的值后,在内存中确实g_ready的值已经变为1了,但是在刚刚那个寄存器中,最开始将g_ready的值也就是0存进去的,一直都没有改变过,但是呢,由于编译器的优化,每次在使用g_ready的变量的时候,都是从寄存器中直接取出值来使用…

说到这里,应该都能够明白了,此时从寄存器中取出的值就一直都是0.然后while一直循环。

3 解决方案

编译的时候使用-O3选项是很常用的。那么如何才能既使用这个-O3选项,又使得上述程序按照我们的意愿来执行呢?volatile就此出场。

使用volatile关键字修饰可能被意外修改的变量(内存),从而禁止编译器对该变量进行优化。

  • volatile一般修饰的是一种易变的变量
  • volatile可理解为一种编译器警告指示字,它告诉编译器必须每次都直接去内存中取变量值。

那么在上述的代码中,我们将main.c中的extern const int g_ready; 改为:extern volatile const int g_ready; 再重新进行优化的编译,然后运行,结果就是正确的运行。可以自己尝试做实验!

4 拓展: const和volatile

细心的朋友会发现,在main.c程序中,我们改完后,成这样了:extern volatile const int g_ready; 又是const又是volatile的。我们上面说过,volatile修饰的是意变的变量,而const修饰的变量是不能被修改的,有的人叫它常量,不可变的。而实际上在编程语言中的解释是这样的:const修饰的变量在当前文件中不能够出现在赋值符号的左边。 所以我们可以看到,在main.c这个文件中g_ready这个变量,并没有出现在赋值符号的左边,所以是没有问题。但是在device.c文件中g_ready是没有被const修饰的,它就可以出现在赋值符号的左边,可以被改变。这样一来,在device.c中修改g_ready这个变量,在main.c中的g_ready也间接被改变了。所以,我觉得说const修饰的是常量这个说法不够准确,说它是变量但是不能够出现在赋值符号的左边更加准确。

下面就总结一下const和volatile关键字:

  • const表示修饰的变量在当前文件中不能出现在赋值符号的左边,不能直接被改变,但是可以间接被改变
  • volatile表示修饰的变量每次使用的时候直接从内存中读取
  • const和volatile同时修饰变量时互不影响。

4 总结

  • 编译优化时,编译器仅根据当前文件进行优化
  • 编译器的优化策略可能造成一些意外
  • volatile强制编译器必须从内存中取变量值
  • const和volatile同时修改变量时,互相不影响彼此的含义。

【软件开发底层知识修炼】二十八 C/C++中volatile的作用相关推荐

  1. 【软件开发底层知识修炼】十八 快速学习GDB调试五 使用GDB进行调试的一些小技巧

    上一篇文章学习了如何使用GDB进行函数调用栈的查看:[软件开发底层知识修炼]十六 快速学习GDB调试四 使用GDB进行函数调用栈的查看 本篇文章是GDB调试快速学习系列的最后一篇.将综合前几篇文章做一 ...

  2. 【软件开发底层知识修炼】十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录

    本文记录之前写过的5篇关于GDB快速学习的文章,从第一篇开始学习到最后一篇,保证可以从入门GDB调试到熟练掌握GDB调试的技巧. 学习交流加 个人qq: 1126137994 个人微信: liu112 ...

  3. 【软件开发底层知识修炼】十五 快速学习GDB调试二 使用GDB进行断点调试

    上一篇文章我们学习了使用GDB的最基本方法:[软件开发底层知识修炼]十四 快速学习GDB调试一 入门使用 本篇文章将学习GDB的断点调试.断点调试是一种非常重要的调试方法. 文章目录 1 断点类型 2 ...

  4. 【软件开发底层知识修炼】十六 快速学习GDB调试三 使用GDB的数据断点监测变量是否改变

    上一篇文章我们学习了如何使用GDB进行软件断点调试和硬件断点调试:[软件开发底层知识修炼]十五 快速学习GDB调试二 使用GDB进行断点调试 本篇文章继续上一篇文章的学习,如何使用GDB的数据断点监测 ...

  5. 【软件开发底层知识修炼】十四 快速学习GDB调试一 入门使用

    前面几篇文章学习了链接器相关的内容.现在开始来学习GDB调试.我们的目的是通过这几篇文章将GDB调试完全学会. 文章目录 1 为什么需要GDB 2 GDB 的常规应用 3 GDB调试程序实例 4 总结 ...

  6. 【软件开发底层知识修炼】十二 C/C++语言中内嵌汇编语言(asm)

    上一篇文章学习了链接脚本的语法与相关概念:链接脚本的概念 在继续学习链接器的内容的同时,先学习一个新内容:内嵌汇编. GCC编译器一般支持C/C++内嵌汇编语言,这样可以实现语言本身无法实现的内容.我 ...

  7. 【软件开发底层知识修炼】十 链接器-main函数不是第一个被执行的函数

    上一篇文章,大概了解了链接器的工作内容就是:符号解析和重定位.点击上一篇文章查看:点击查看. 本片文章其实还是围绕链接器来学习.只不过不是很明显,当你学到下一篇文章时,就明白了. 本篇文章来弄明白一个 ...

  8. 【软件开发底层知识修炼】二十 深入理解可执行程序的结构

    上一篇文章记录了GDB调试从入门到熟练掌握的学习全过程.点击链接查看:[软件开发底层知识修炼]十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录 还记得在以前的学习Binutils工具的时候,学 ...

  9. 【软件开发底层知识修炼】十三 链接器-如何写出不依赖C库函数的代码

    本文将综合以下4篇文章,学习如何写出不依赖libc库的程序: [软件开发底层知识修炼]九 链接器-可重定位文件与可执行文件 [软件开发底层知识修炼]十 链接器-main函数不是第一个被执行的函数 [软 ...

最新文章

  1. MySQL修改和查看表类型
  2. 项目中WebService使用Spring容器的配置
  3. 鸿蒙能够替代安卓吗,华为鸿蒙2.0可以替代安卓吗,华为鸿蒙2.0优势在哪
  4. redux源码分析之一:createStore.js
  5. Linux(8) —— grep命令
  6. JAX-RS 2.0中的透明PATCH支持
  7. linux安装mysql(shell一键安装)
  8. Spring Boot(十)Logback和Log4j2集成与日志发展史
  9. mysql 执行计划_mysql执行计划
  10. 关系数据库查看器应用程序-ASP.NET Core
  11. Win10 64位安装SQL2000(个人版)
  12. 分布式消息中间件中的一些概念(接上一篇的《什么是分布式消息中间件?》)...
  13. URAL 1081 Binary Lexicographic Sequence
  14. JDK源码学习笔记——String
  15. jquery 的$()
  16. 网上第3方软件教程摘选
  17. Android 自定义View关于measure流程的基本思路整理
  18. 从 Storm 到 Flink,汽车之家基于 Flink 的实时 SQL 平台设计思路与实践
  19. (详解)IDEA中使用git教程
  20. 企业如何选择SSL证书?

热门文章

  1. 第二十八期:阿里云VS腾讯云 谁才是中国未来的云计算之王?
  2. Linux 环境变量PATH设置和查看etc/profile和bashrc的区别
  3. window mysql集群视频_windows7实现mysql集群cluster-mysql簇
  4. linux重启memcache_Linux中安装Memcached服务的方法
  5. imagex使用方法_Microsoft OneDrive 的使用心得,真香
  6. 在laravel5.8中集成swoole组件----用协程实现的服务端和客户端(一)
  7. SpringCloud的学习记录(1)
  8. django后台数据管理admin设置代码
  9. DSO windowed optimization 代码 (3)
  10. 多线程——线程间的同步通信