c语言编译优化引发问题

问题描述

同样的代码,不优化编译,可以正常执行,经过-O2优化编译后,代码被卡住.整体功能涉及多进程,多线程操作.

问题发现

经过加打印,发现卡在while(a!=0);//死循环,等待特殊事件发生来解开循环
a初始化为-1;
过一会后,另外有个线程,当特定事件发生的时候,将a置为0;
通过加打印,确定当特定事件发生的时候a确实变为了0.
此时,按照代码逻辑,while循环应该结束了.

实际效果是:优化后的代码,在a=0后,依然在while循环.(当前线程没有感知到a已经发生了变化)
关闭优化,重新编译运行,逻辑又恢复正常.

解决

加volatile修饰变量.涉及多线程,多进程共享的变量,加volatile.
特别是多线程,生产者线程写队列,消费者线程读队列,主要的成员,禁止优化
在这里插入图片描述

volatile的本意是“易变的”

  由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
static int flag=0;
int main(void)
{
	...
	while (flag == 0);//等待其他线程通知
	do_something();
	
}

//其他线程
void pthread_2(void)
{
	flag=1;
}
一般说来,volatile用在如下的几个地方:

1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。

volatile 的含义

volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化,volatile的字面含义是易变的,它有下面的作用:
1 不会在两个操作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变,编译器自己无法知道,volatile就是告诉编译器这种情况。
2 不做常量合并、常量传播等优化,所以像下面的代码:

volatile int i = 1;
if (i > 0) ...

if的条件不会当作无条件真。
3 对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值操作,然而对Memory Mapped IO的处理是不能这样优化的。
前面有人说volatile可以保证对内存操作的原子性,这种说法不大准确,其一,x86需要LOCK前缀才能在SMP下保证原子性,其二,RISC根本不能对内存直接运算,要保证原子性得用别的方法,如atomic_inc。
对于jiffies,它已经声明为volatile变量,我认为直接用jiffies++就可以了,没必要用那种复杂的形式,因为那样也不能保证原子性。
你可能不知道在Pentium及后续CPU中,下面两组指令

inc jiffies 
;;
mov jiffies, ?x
inc ?x
mov ?x, jiffies

作用相同,但一条指令反而不如三条指令快。

编译器优化 → C关键字volatile → memory破坏描述符zz

“memory”比较特殊,可能是内嵌汇编中最难懂部分。为解释清楚它,先介绍一下编译器的优化知识,再看C关键字volatile。最后去看该描述符。
1、编译器优化介绍
内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬件重新排序引起的问题的解决办法是在从硬件(或者其他处理器)的角度看必须以特定顺序执行的操作之间设置内存屏障(memory barrier),linux 提供了一个宏解决编译器的执行顺序问题。
void Barrier(void)
这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。
2、C语言关键字volatile
C语言关键字volatile(注意它是用来修饰变量而不是上面介绍的__volatile__)表明某个变量的值可能在外部被改变,因此对这些变量的存取不能缓存到寄存器,每次使用时需要重新存取。该关键字在多线程环境下经常使用,因为在编写多线程的程序时,同一个变量可能被多个线程修改,而程序通过该变量同步各个线程,例如:

DWORD __stdcall threadFunc(LPVOID signal)
{
int* intSignal=reinterpret_cast<int*>(signal);
*intSignal=2;
while(*intSignal!=1)
sleep(1000);
return 0;
}

该线程启动时将intSignal 置为2,然后循环等待直到intSignal 为1 时退出。显然intSignal的值必须在外部被改变,否则该线程不会退出。但是实际运行的时候该线程却不会退出,即使在外部将它的值改为1,看一下对应的伪汇编代码就明白了:

mov ax,signal
label:
if(ax!=1)
goto label

对于C编译器来说,它并不知道这个值会被其他线程修改。自然就把它cache在寄存器里面。记住,C 编译器是没有线程概念的!这时候就需要用到volatile。volatile 的本意是指:这个值可能会在当前线程外部被改变。也就是说,我们要在threadFunc中的intSignal前面加上volatile关键字,这时候,编译器知道该变量的值会在外部改变,因此每次访问该变量时会重新读取,所作的循环变为如下面伪码所示:

label:
mov ax,signal
if(ax!=1)
goto label

3、Memory
有了上面的知识就不难理解Memory修改描述符了,Memory描述符告知GCC:
1)不要将该段内嵌汇编指令与前面的指令重新排序;也就是在执行内嵌汇编代码之前,它前面的指令都执行完毕
2)不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变量会以不可预知的方式发生改变,因此GCC插入必要的代码先将缓存到寄存器的变量值写回内存,如果后面又访问这些变量,需要重新访问内存。
如果汇编指令修改了内存,但是GCC 本身却察觉不到,因为在输出部分没有描述,此时就需要在修改描述部分增加“memory”,告诉GCC 内存已经被修改,GCC 得知这个信息后,就会在这段指令之前,插入必要的指令将前面因为优化Cache 到寄存器中的变量值先写回内存,如果以后又要使用这些变量再重新读取。
使用“volatile”也可以达到这个目的,但是我们在每个变量前增加该关键字,不如使用“memory”方便。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/219524.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【云备份】客户端实现 及 项目整体总结

文章目录 客户端客户端实现思想客户端文件操作类的设计与拷贝Util.hpp的设计data.hpp的设计Storage —— 持久化存储Initload——数据初始化加载 cloud.hpp的设计GetFileIdentifier——创建文件唯一标识Upload—— 文件上传IsNeedupload —— 客户端文件是否需要上传判断RunMod…

【原创分享】高功率电源PCB设计中变压器下方走线的关键技巧

高功率电源的设计中&#xff0c;变压器起到了电能的传递与转换的重要作用。变压器下方的走线设计不仅涉及到电路的功率传输效率&#xff0c;还与电磁兼容性&#xff08;EMC&#xff09;、热管理以及电路的可靠性密切相关。 1. 走线布局 在进行变压器下方走线设计时&#xff0c…

Vmware虚拟机简介和安装

作者&#xff1a;余小小 常见的虚拟机 vmwarevirtualBox Vmware 运行在win系统上centos运行在Vm上 先安装vm&#xff0c;在安装centos系统 Vmware介绍 不用分区或者重开机&#xff0c;就可以在同一台pc上使用多种操作系统完全隔离&#xff0c;且保护不同的操作系统环境和文…

Kubernetes常用工作负载控制器

文章目录 一、常用负载控制器是什么二、Deployment控制器1.介绍2.使用流程3.应用部署4.应用升级5.滚动升级实现原理&#xff08;replicaset控制器&#xff09;6.滚动升级实现流程7.滚动升级策略8.应用实例扩容和缩容9.应用发布失败回滚10.应用下线 三、DaemonSet控制器四、Job控…

Linux修改时区失败,手动修改localtime无效

有时候改了这个也不行&#xff0c;用命令行修改也不行 解决办法 &#xff1a;cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 或者想改其他时区的直接 ll /usr/share/zoneinfo/ 查看

2023年1月18日 Go生态洞察:开发者的声音与Go的进化

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

WebGL开发交互式艺术品技术方案

开发交互式艺术品需要使用 WebGL 技术&#xff0c;并结合其他前端技术以实现丰富的用户体验。以下是一个可能的技术方案&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.WebGL 框架&#xff1a; 选…

AWS攻略——子网

文章目录 分配子网给Public子网分配互联网网关创建互联网网关附加到VPC 给Public子网创建路由表关联子网 打通Public子网和互联网网关 创建Public子网下的EC2进行测试配置Private子网路由给Private子网创建路由表附加在Private子网 创建Private子网下的EC2进行测试创建实例在跳…

Cannot resolve com.lxz.springcloud:cloud-api-commons:1.0-SNAPSHOT

原因可能是groupId等信息写错了 导入的jar包的groupId要与它自己的坐标匹配

创新、升级丨数据手套FOHEART Pro开启手势识别新篇章!

在人机交互领域&#xff0c;我们始终追求更加自然、逼真的体验。正如现实生活中&#xff0c;我们习惯于通过语言和表情来传达思想和情感&#xff0c;然而&#xff0c;在虚拟世界中&#xff0c;人机交互需要以更加直观、生动的方式进行操作、控制和交互。 为了更好地满足市场的…

Isaac Sim教程07 拓展编程Extension

Isaac Sim 拓展编程Extension 版权信息 Copyright 2023 Herman YeAuromix. All rights reserved.This course and all of its associated content, including but not limited to text, images, videos, and any other materials, are protected by copyright law. The aut…

出海风潮:中国母婴品牌征服国际市场的机遇与挑战!

近年来&#xff0c;中国母婴品牌在国内市场蓬勃发展的同时&#xff0c;也逐渐将目光投向国际市场。这一趋势不仅受益于中国经济的崛起&#xff0c;还得益于全球市场对高质量母婴产品的不断需求。然而&#xff0c;面对国际市场的机遇&#xff0c;中国母婴品牌同样面临着一系列挑…

【稳定检索|投稿优惠】2024年光电信息与机器人发展国际会议(ICOIRD 2024)

2024年光电信息与机器人发展国际会议(ICOIRD 2024) 2024 International Conference on Optoelectronic Information and Robot Development(ICOIRD 2024) 一、【会议简介】 信息技术与人工智能的浪潮正在激荡&#xff0c;不断刷新我们生活的页面&#xff0c;深刻烙印在光电信息…

如何为 3D 模型制作纹理的最佳方法

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 您可以通过不同的方式为 3D 模型创建 3D 纹理。下面我们将介绍为 3D …

Django回顾6

目录 一.Session 1.什么是Session 2.Django中Session相关方法 3.Django中的Session配置 二.中间件 1.什么是中间件 中间件的定义 2.中间件有什么用 3.自定义中间件 process_request和process_reponse &#xff08;1&#xff09;导入 &#xff08;2&#xff09;自定义…

分享116个图片JS特效,总有一款适合您

分享116个图片JS特效&#xff0c;总有一款适合您 116个图片JS特效下载链接&#xff1a;https://pan.baidu.com/s/1WvUvmG1adR2EJG97MiGj3A?pwd6666 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整…

网卡bonding绑定

目录 一、概念 1、概述&#xff1a; 二、实验 1、绑定案例&#xff1a; 一、概念 1、概述&#xff1a; 将多个物理网卡进行排列组合&#xff0c;形成逻辑网卡&#xff0c;网卡的高可用 绑定模式 mode0&#xff08;平衡负载模式&#xff09;&#xff1a;平时两块网卡均工…

安全AI系统开发指南

执行摘要 本文件建议为使用人工智能&#xff08;AI&#xff09;的任何系统的提供商提供指导方针&#xff0c;无论这些系统是从头开始创建的&#xff0c;还是建立在他人提供的工具和服务之上的。实施这些指导方针将有助于提供商构建按预期运行、在需要时可用的人工智能系统&…

“上云”还是“下云”?探云计算的下一站未来!

引言 10 月 27 日&#xff0c;X&#xff08;原Twitter&#xff09;工程技术发布帖子称&#xff0c;在过去的一年里&#xff0c;技术团队优化了 X 的云服务使用方式&#xff0c;着手将更多工作负载迁往本地基础设施。这一转变使 X 每月的云成本降低了 60%。所有媒体、Blob 存储均…

docker搭建xxl-job

使用docker-compose创建并运行xxl-job 查看、下载镜像 docker search xxl-job # 结果&#xff0c;自己指定版本 xuxueli/xxl-job-admin:2.3.1创建文件夹 /usr/local/software/xxl-job/logs编排docker-compose文件 version: 2 networks:wn_docker_net:external: true servic…