【软件开发底层知识修炼】二十八 C/C++中volatile的作用
- 上一篇文章学习了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的作用相关推荐
- 【软件开发底层知识修炼】十八 快速学习GDB调试五 使用GDB进行调试的一些小技巧
上一篇文章学习了如何使用GDB进行函数调用栈的查看:[软件开发底层知识修炼]十六 快速学习GDB调试四 使用GDB进行函数调用栈的查看 本篇文章是GDB调试快速学习系列的最后一篇.将综合前几篇文章做一 ...
- 【软件开发底层知识修炼】十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录
本文记录之前写过的5篇关于GDB快速学习的文章,从第一篇开始学习到最后一篇,保证可以从入门GDB调试到熟练掌握GDB调试的技巧. 学习交流加 个人qq: 1126137994 个人微信: liu112 ...
- 【软件开发底层知识修炼】十五 快速学习GDB调试二 使用GDB进行断点调试
上一篇文章我们学习了使用GDB的最基本方法:[软件开发底层知识修炼]十四 快速学习GDB调试一 入门使用 本篇文章将学习GDB的断点调试.断点调试是一种非常重要的调试方法. 文章目录 1 断点类型 2 ...
- 【软件开发底层知识修炼】十六 快速学习GDB调试三 使用GDB的数据断点监测变量是否改变
上一篇文章我们学习了如何使用GDB进行软件断点调试和硬件断点调试:[软件开发底层知识修炼]十五 快速学习GDB调试二 使用GDB进行断点调试 本篇文章继续上一篇文章的学习,如何使用GDB的数据断点监测 ...
- 【软件开发底层知识修炼】十四 快速学习GDB调试一 入门使用
前面几篇文章学习了链接器相关的内容.现在开始来学习GDB调试.我们的目的是通过这几篇文章将GDB调试完全学会. 文章目录 1 为什么需要GDB 2 GDB 的常规应用 3 GDB调试程序实例 4 总结 ...
- 【软件开发底层知识修炼】十二 C/C++语言中内嵌汇编语言(asm)
上一篇文章学习了链接脚本的语法与相关概念:链接脚本的概念 在继续学习链接器的内容的同时,先学习一个新内容:内嵌汇编. GCC编译器一般支持C/C++内嵌汇编语言,这样可以实现语言本身无法实现的内容.我 ...
- 【软件开发底层知识修炼】十 链接器-main函数不是第一个被执行的函数
上一篇文章,大概了解了链接器的工作内容就是:符号解析和重定位.点击上一篇文章查看:点击查看. 本片文章其实还是围绕链接器来学习.只不过不是很明显,当你学到下一篇文章时,就明白了. 本篇文章来弄明白一个 ...
- 【软件开发底层知识修炼】二十 深入理解可执行程序的结构
上一篇文章记录了GDB调试从入门到熟练掌握的学习全过程.点击链接查看:[软件开发底层知识修炼]十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录 还记得在以前的学习Binutils工具的时候,学 ...
- 【软件开发底层知识修炼】十三 链接器-如何写出不依赖C库函数的代码
本文将综合以下4篇文章,学习如何写出不依赖libc库的程序: [软件开发底层知识修炼]九 链接器-可重定位文件与可执行文件 [软件开发底层知识修炼]十 链接器-main函数不是第一个被执行的函数 [软 ...
最新文章
- MySQL修改和查看表类型
- 项目中WebService使用Spring容器的配置
- 鸿蒙能够替代安卓吗,华为鸿蒙2.0可以替代安卓吗,华为鸿蒙2.0优势在哪
- redux源码分析之一:createStore.js
- Linux(8) —— grep命令
- JAX-RS 2.0中的透明PATCH支持
- linux安装mysql(shell一键安装)
- Spring Boot(十)Logback和Log4j2集成与日志发展史
- mysql 执行计划_mysql执行计划
- 关系数据库查看器应用程序-ASP.NET Core
- Win10 64位安装SQL2000(个人版)
- 分布式消息中间件中的一些概念(接上一篇的《什么是分布式消息中间件?》)...
- URAL 1081 Binary Lexicographic Sequence
- JDK源码学习笔记——String
- jquery 的$()
- 网上第3方软件教程摘选
- Android 自定义View关于measure流程的基本思路整理
- 从 Storm 到 Flink,汽车之家基于 Flink 的实时 SQL 平台设计思路与实践
- (详解)IDEA中使用git教程
- 企业如何选择SSL证书?
热门文章
- 第二十八期:阿里云VS腾讯云 谁才是中国未来的云计算之王?
- Linux 环境变量PATH设置和查看etc/profile和bashrc的区别
- window mysql集群视频_windows7实现mysql集群cluster-mysql簇
- linux重启memcache_Linux中安装Memcached服务的方法
- imagex使用方法_Microsoft OneDrive 的使用心得,真香
- 在laravel5.8中集成swoole组件----用协程实现的服务端和客户端(一)
- SpringCloud的学习记录(1)
- django后台数据管理admin设置代码
- DSO windowed optimization 代码 (3)
- 多线程——线程间的同步通信