关键字volatile作用和用法

目录

一、多线程编程中的volatile关键字

二、嵌入式编程中的volatile关键字

三、 优化编译器优化

四、 指针类型转换


        一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。

        精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量在内存中的值,而不是使用保存在寄存器里的备份(虽然读写寄存器比读写内存快)。

        它主要用于处理与多线程、中断处理和硬件寄存器等相关的情况。

  1. 防止编译器优化:编译器在优化代码时会尝试将变量的访问操作优化为更高效的方式,例如将变量的值缓存在寄存器中。然而,对于某些特殊的变量,如多线程环境下的共享变量、中断处理中的标志位、硬件寄存器等,这种优化可能会导致意外的行为。使用 volatile 关键字可以告诉编译器不要对该变量进行优化,确保每次访问都从内存中读取或写入。

  2. 处理多线程共享变量:在多线程编程中,当一个变量被多个线程共享并且可能被一个线程修改时,需要使用 volatile 关键字来确保线程之间的可见性。这样可以防止编译器对共享变量的优化,确保每个线程都能正确地读取到最新的值。

  3. 处理中断和硬件寄存器:在中断处理程序中,某些变量可能由硬件直接修改,而不是通过常规的变量赋值操作。在这种情况下,使用 volatile 关键字可以确保编译器不会对这些变量的访问进行优化,以避免出现不一致的行为。

一、多线程编程中的volatile关键字

        最常见的用途之一是在多线程编程中,通过volatile关键字告知编译器不要对变量进行优化,以避免出现意外的行为。例如,在多线程环境中,一个线程可能会修改某个变量,而另一个线程在不知情的情况下使用了这个变量。下面是一个示例,展示了在多线程编程中使用volatile的情况:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>volatile int sharedValue = 0;void *threadFunction(void *arg)
{sharedValue = 10;return NULL;
}int main()
{pthread_t thread;pthread_create(&thread, NULL, threadFunction, NULL);while (sharedValue != 10){printf("1\n");//sleep(1);}printf("sharedValue has been modified.\n");pthread_join(thread,NULL);return 0;
}

编译代码:

gcc test_volatile.c -pthread 

 执行代码:

        在这段代码中,volatile关键字的作用是防止编译器对sharedValue变量进行优化。在多线程环境中,一个线程可能会在另一个线程改变sharedValue的值之前,就已经读取了sharedValue的值。如果编译器对sharedValue进行优化,可能会导致一个线程读取到的是旧的、已经被优化过的sharedValue的值,而不是最新的值。

        volatile关键字告诉编译器,sharedValue是一个易变的变量,可能会被程序的其他部分(例如其他线程、硬件设备等)改变。因此,每次引用sharedValue时,都需要直接从它在内存中的地址读取,而不能依赖可能已经过时的、保存在寄存器中的值

        在这段代码中,volatile确保了主线程能够看到子线程对sharedValue的修改。如果没有volatile,编译器可能会认为主线程的循环是无效的(因为它可能认为sharedValue的值没有改变),并可能优化掉这个循环,导致程序的行为不正确。所以,volatile在这里是必要的,以确保程序的正确性。

二、嵌入式编程中的volatile关键字

        在嵌入式编程中,硬件寄存器和内存映射设备常常需要使用volatile关键字,以确保编译器不会对其进行优化,从而保证与硬件的交互是准确的。以下是一个示例,展示了在嵌入式环境中使用volatile关键字的情况:

#include <stdio.h>#define GPIO_PORT ((volatile unsigned int *)0x12345678)int main() {*GPIO_PORT = 0xFF; // 将端口设置为全高电平// 假设在这里进行了一些与硬件相关的操作unsigned int value = *GPIO_PORT; // 读取端口的值printf("Value read from GPIO_PORT: %u\n", value);return 0;
}

 

        这段代码试图直接访问物理地址0x12345678,这个地址是一个GPIO端口的地址。在大多数现代操作系统中,用户空间的程序是不能直接访问物理内存的,这是由操作系统的内存保护机制所决定的。当程序试图直接访问一个它没有权限访问的内存地址时,操作系统会产生一个段错误(Segmentation Fault)。

        在这段代码中,*GPIO_PORT = 0xFF;unsigned int value = *GPIO_PORT;这两行代码都试图直接访问物理地址0x12345678,这是不被允许的,所以会产生段错误。

        如果你想在用户空间的程序中访问硬件,你需要使用特定的系统调用,或者使用某种方法将硬件映射到你的进程的地址空间中。具体的方法取决于你的硬件和操作系统。在Linux中,你可以使用mmap()函数将硬件映射到你的进程的地址空间中。在嵌入式系统中,你可能需要使用特定的库或者驱动程序来访问硬件。

        在这段代码中,volatile关键字的作用是防止编译器对GPIO_PORT进行优化。volatile告诉编译器,GPIO_PORT是一个易变的变量,可能会被程序的其他部分(例如其他线程、硬件设备等)改变。因此,每次引用GPIO_PORT时,都需要直接从它在内存中的地址读取,而不能依赖可能已经过时的、保存在寄存器中的值。

        在这段代码中,volatile确保了对GPIO_PORT的读写操作都是直接对内存进行的,而不会被编译器优化掉。这在硬件编程中是非常重要的,因为硬件的状态可能会在任何时候改变,我们需要确保每次读取或写入硬件时,都是获取或设置的最新的状态。如果没有volatile,编译器可能会认为某些对GPIO_PORT的读写操作是无效的(因为它可能认为GPIO_PORT的值没有改变),并可能优化掉这些操作,导致程序的行为不正确。所以,volatile在这里是必要的,以确保程序的正确性。

三、 优化编译器优化

        在某些情况下,我们可能希望关闭编译器的某些优化,以便更好地进行调试或者对代码进行性能分析。volatile关键字可以在这方面发挥作用。以下是一个示例,展示了如何使用volatile来关闭编译器优化: 

#include <stdio.h>volatile int debugFlag = 0;void debugPrint(const char *message) {if (debugFlag) {printf("Debug: %s\n", message);}
}int main() {debugFlag = 1;debugPrint("This is a debug message.");return 0;
}

        在这段代码中,volatile关键字的作用是防止编译器对debugFlag进行优化。volatile告诉编译器,debugFlag是一个易变的变量,可能会被程序的其他部分(例如其他线程、硬件设备等)改变。因此,每次引用debugFlag时,都需要直接从它在内存中的地址读取,而不能依赖可能已经过时的、保存在寄存器中的值。

        在这段代码中,volatile确保了对debugFlag的读写操作都是直接对内存进行的,而不会被编译器优化掉。这在调试代码时是非常重要的,因为debugFlag的状态可能会在任何时候改变,我们需要确保每次读取或写入debugFlag时,都是获取或设置的最新的状态。如果没有volatile,编译器可能会认为某些对debugFlag的读写操作是无效的(因为它可能认为debugFlag的值没有改变),并可能优化掉这些操作,导致程序的行为不正确。所以,volatile在这里是必要的,以确保程序的正确性。

四、 指针类型转换

        有时候,我们可能需要在指针类型之间进行转换,而编译器会认为这是不安全的操作,从而导致编译错误。使用volatile关键字可以告知编译器,这个类型转换是有意义的,不应该引发错误。以下是一个示例:

#include <stdio.h>int main() {int value = 42;int *volatile volatileIntPtr = &value;void *voidPtr = (void *)volatileIntPtr;int *newValuePtr = (int *)voidPtr;printf("New value: %d\n", *newValuePtr);return 0;
}

 

        在这个例子中,将int指针转换为void指针,然后再转回int指针。在这种情况下,使用volatile关键字可能会更合适,因为编译器不会对void指针的转换进行优化。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Docker】Swarm的ingress网络

Docker Swarm Ingress网络是Docker集群中的一种网络模式&#xff0c;它允许在Swarm集群中运行的服务通过一个公共的入口点进行访问。Ingress网络将外部流量路由到Swarm集群中的适当服务&#xff0c;并提供负载均衡和服务发现功能。 在Docker Swarm中&#xff0c;Ingress网络使…

RTL编码(2)——模块优化

一、顶层模块的划分 在RTL编码中&#xff0c;我们是以模块为单位进行设计的&#xff0c;模块之间的连接和嵌套关系对于电路结构有着很大的影响。一个好的系统设计中&#xff0c;我们应该使得模块尽量满足以下两个标准&#xff1a; 顶层模块扁平化内部模块层次化 1.1 顶层模块扁…

系列学习前端之第 3 章:一文精通 css

全套学习 HTMLCSSJavaScript 代码和笔记请下载网盘的资料&#xff1a; 链接: 百度网盘 请输入提取码 提取码: 6666 一、CSS基础 1. CSS简介 CSS 的全称为&#xff1a;层叠样式表 ( Cascading Style Sheets ) 。 CSS 也是一种标记语言&#xff0c;用于给 HTML 结构设…

使用arcpy移除遥感影像云层

先讲思路&#xff0c;然后上代码&#xff1a; 去除云层 思路1&#xff1a; 如果同一地理区域的多个图像&#xff0c;其中一些部分有丰富的云&#xff0c;而另一些部分没有云&#xff0c;则可以将它们组合起来&#xff0c;以便无云的部分替代多云的部分。这种方法很简单&…

C++ vector基本操作

目录 一、介绍 二、定义 三、迭代器 四、容量操作 1、size 2、capacity 3、empty 4、resize 5、reserve 总结&#xff08;扩容机制&#xff09; 五、增删查改 1、push_back & pop_back 2、find 3、insert 4、erase 5、swap 6、operator[] 一、介绍 vector…

前端CSS(层叠样式表)总结

CSS2总结 一、CSS基础 1. CSS简介 CSS 的全称为&#xff1a;层叠样式表 ( Cascading Style Sheets ) 。CSS 也是一种标记语言&#xff0c;用于给 HTML 结构设置样式&#xff0c;例如&#xff1a;文字大小、颜色、元素宽高等等。 简单理解&#xff1a; CSS 可以美化…

<软考>软件设计师-3程序设计语言基础(总结)

(一) 程序设计语言概述 1 程序设计语言的基本概念 1-1 程序设计语言的目的 程序设计语言是为了书写计算机程序而人为设计的符号语言&#xff0c;用于对计算过程进行描述、组织和推导。 1-2 程序语言分类 低级语言 : 机器语言&#xff08;计算机硬件只能识别0和1的指令序列)&…

java设计模式学习之【装饰器模式】

文章目录 引言装饰器模式简介定义与用途实现方式 使用场景优势与劣势装饰器模式在Spring中的应用画图示例代码地址 引言 在日常生活中&#xff0c;我们常常对基本事物添加额外的装饰以增强其功能或美观。例如&#xff0c;给手机加一个保护壳来提升其防护能力&#xff0c;或者在…

SwiftUI 中创建一个自定义文件管理器只需4步!你敢信!?

概览 在 SwiftUI 中写一个自定义文件内容的管理器有多难呢&#xff1f; 答案可能超乎小伙伴们的想象&#xff1a;仅需4步&#xff01;可谓是超级简单&#xff01; 在本篇博文中&#xff0c;您将学到如下内容&#xff1a; 概览1. 第一步&#xff1a;定义文件类型2. 第二步&…

Dockerfile 指令的最佳实践

这些建议旨在帮助您创建一个高效且可维护的Dockerfile。 一、FROM 尽可能使用当前的官方镜像作为镜像的基础。Docker推荐Alpine镜像&#xff0c;因为它受到严格控制&#xff0c;体积小&#xff08;目前不到6 MB&#xff09;&#xff0c;同时仍然是一个完整的Linux发行版。 FR…

从主从复制到哨兵模式(含Redis.config配置模板)

文章目录 前言一、主从复制1.概述2.作用3.模拟实践搭建场景模拟实践 二、哨兵模式1.概述2.配置使用3.优缺点4.sentinel.conf完整配置 总结 前言 从主从复制到哨兵模式。 一、主从复制 1.概述 主从复制&#xff0c;是指将一台 Redis 服务器的数据&#xff0c;复制到其他的 Red…

0010Java安卓程序设计-ssm基于安卓的掌上校园系统

文章目录 **摘要**目录系统实现5.2管理员功能模块开发环境 编程技术交流、源码分享、模板分享、网课分享 企鹅&#x1f427;裙&#xff1a;776871563 摘要 随着Internet的发展&#xff0c;人们的日常生活已经离不开网络。未来人们的生活与工作将变得越来越数字化&#xff0c;…

Livox_ros_driver2 消息 (msg) 类型对 SLAM 应用程序的适配

Title: Livox_ros_driver2 消息 (msg) 类型对 SLAM 应用程序的适配 文章目录 I 前言II. 查看 ROS 消息III. Livox ROS 驱动的消息类型IV. 适配修改应用程序V. 总结 I 前言 有时候, 拿到最新的 Livox 激光传感器, 比如 HAP, 原厂也提供了 ROS 驱动支持 livox_ros_drivers2 (htt…

UE蓝图 里的函数,编辑模式在Sequence里执行

在蓝图里创建CustomFunction &#xff0c;把蓝图拖入Sequence &#xff0c;添加事件 即可调用 如果需要在Sequence里K 蓝图里的变量&#xff0c;需要勾上向过场动画公开

JS中Map对象与object的区别

若想了解Map对象可以阅读本人这篇ES6初步了解Map Map对象与object有什么区别&#xff1f;让我为大家介绍一下吧&#xff01; 共同点 二者都是以key-value的形式对数据进行存储 const obj {name:"zs",age:18}console.log(obj)let m new Map()m.set("name&quo…

《深入理解计算机系统》学习笔记 - 第三课 - 浮点数

Floating Point 浮点数 文章目录 Floating Point 浮点数分数二进制示例能代表的数浮点数的表示方式浮点数编码规格化值规格化值编码示例 非规格化的值特殊值 示例IEEE 编码的一些特殊属性四舍五入&#xff0c;相加&#xff0c;相乘四舍五入四舍五入的模式二进制数的四舍五入 浮…

带负离子的高速吹风筒方案介绍---【其利天下技术】

负离子吹风筒的产品概念&#xff0c;在吹风筒的产品系列里早就存在的。近年来&#xff0c;随着高速风筒的逐渐普及&#xff0c;产品商都开始把这些产品概念带了进来。一方面提升产品的核心竞争力&#xff0c;另一方面也是为了提升产品体验度&#xff0c;给用户带来不一样的产品…

夯实c基础

夯实c基础 区别&#xff1a; 图一的交换&#xff0c;&#xff08;交换的是地址而不是两数&#xff09;无法实现两数的交换。 题干以下程序的输出结果为&#xff08; c  &#xff09;。 void fun(int a, int b, int c){ ca*b; } void main( ){ int…

ssh安装和Gitee(码云)源码拉取

文章目录 安装ssh服务注册码云公钥设置码云账户SSH公钥安装git客户端和git-lfs源码获取 安装ssh服务 更新软件源&#xff1a; sudo apt-get update安装ssh服务 sudo apt-get install openssh-server检查ssh是否安装成功 which ssh输出&#xff1a; /usr/bin/ssh启动ssh 服…

虾皮定价公式:如何在Shopee上为您的商品设置合适的价格?

在Shopee这样的电商平台上销售商品&#xff0c;定价是一个至关重要的因素。一个合理的定价策略不仅可以吸引更多的买家&#xff0c;还可以确保您从每个商品中获得足够的利润。虾皮&#xff08;Shopee&#xff09;为卖家提供了一个简化的定价公式&#xff0c;考虑了多个因素&…