C 语言中的 volatile 关键字

news2025/4/19 0:18:44

1、概念

  volatile 是 C/C++ 语言中的一个类型修饰符,用于告知编译器:该变量的值可能会在程序控制流之外被意外修改(如硬件寄存器、多线程共享变量或信号处理函数等),因此编译器不应对其进行激进的优化(如缓存到寄存器或消除冗余读取)。

  在程序运行时,编译器通常会假设变量的值仅由当前线程或函数内的代码修改,并据此进行优化(如循环内变量提升、指令重排等)。然而,在嵌入式开发、设备驱动编程或多线程环境中,某些变量的值可能被外部因素(如硬件中断、信号处理器、其他线程)异步修改。此时,若未使用 volatile 修饰,编译器可能生成错误的优化代码,导致程序行为异常。

简而言之,volatile 的作用是:

  • 阻止编译器优化:强制每次访问变量时都从内存读取,而非使用寄存器中的缓存值;
  • 确保内存可见性:防止编译器重排或省略对变量的访问,保证操作顺序符合预期;
  • 适用于特殊场景:如硬件寄存器映射、信号处理、多线程共享变量(需配合其他同步机制)

volatile 并不解决所有并发问题(如原子性),但它是底层编程中确保正确内存访问的重要工具。

2、代码测试

下面是在 ARM 平台的 C 语言测试,因为 ARM 是弱内存模型,更容易复现问题。

/*
 *  volatile_test.c
 */

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

// 全局变量,使用或不使用volatile修饰
int flag = 0;  // 尝试改为 volatile int flag = 0; 观察不同结果

void handler(int sig) {
    flag = 1;
    printf("Signal handler set flag to 1\n");
}

int main() {
    signal(SIGALRM, handler);
    alarm(1);  // 1秒后发送SIGALRM信号

    while(!flag) {
        // 空循环等待flag变化
    }

    printf("Main loop detected flag change\n");
    return 0;
}

2.1 测试结果

不使用 volatile 关键字,程序会卡死在 while 循环中:

liang@liang-virtual-machine:~/cfp$../arm-none-linux-gnueabi-gcc -O3 -g -o volatile_test volatile_test.c
liang@liang-virtual-machine:~/cfp$ ./volatile_test
Signal handler set flag to 1

使用 volatile,程序正常退出

liang@liang-virtual-machine:~/cfp$../arm-none-linux-gnueabi-gcc -O3 -g -o volatile_test volatile_test.c
liang@liang-virtual-machine:~/cfp$ ./volatile_test
Signal handler set flag to 1
Main loop detected flag change
liang@liang-virtual-machine:~/cfp$

2.2 反汇编

不使用 volatile:

liang@liang-virtual-machine:~/cfp$../arm-none-linux-gnueabi-objdump -S volatile_test
......
int main() {
    83cc:	e92d4010 	push	{r4, lr}
    signal(SIGALRM, handler);
    83d0:	e59f1030 	ldr	r1, [pc, #48]	; 8408 <main+0x3c>
    83d4:	e3a0000e 	mov	r0, #14	; 0xe
    83d8:	ebffffc5 	bl	82f4 <_init+0x38>
    alarm(1);  // 1秒后发送SIGALRM信号
    83dc:	e3a00001 	mov	r0, #1	; 0x1
    83e0:	ebffffc9 	bl	830c <_init+0x50>
    83e4:	e59f3020 	ldr	r3, [pc, #32]	; 840c <main+0x40>
    83e8:	e5932000 	ldr	r2, [r3]		      ; 从内存读取 flag 值到 r2
    83ec:	e3520000 	cmp	r2, #0	; 0x0		  ; 比较 r2 的值
    83f0:	1a000000 	bne	83f8 <main+0x2c>	  ; 如果 r2≠0,跳转到 83f8 位置
    83f4:	eafffffe 	b	83f4 <main+0x28>      ; 无条件跳转到自身(无限循环)

    while(!flag) {
        // 空循环等待flag变化
    }

    printf("Main loop detected flag change\n");
    83f8:	e59f0010 	ldr	r0, [pc, #16]	; 8410 <main+0x44>
    83fc:	ebffffc5 	bl	8318 <_init+0x5c>
    return 0;
}
......

  可以看到,编译器对 while 循环做了优化。编译器只在循环开始前读取一次 flag 的值到寄存器 r2。编译器认为 flag 在循环内不会被修改,之后循环中不再重新从内存读取 flag。同时,直接做了一个无条件跳转到自身的优化:

83f4:	eafffffe 	b	83f4 <main+0x28>      ; 无条件跳转到自身(无限循环)

  而对比使用 volatile 关键字,可以看到编译器没有对 while 循环做优化,每次循环都重新读取 flag 的值:

liang@liang-virtual-machine:~/cfp$../arm-none-linux-gnueabi-objdump -S volatile_test
......
int main() {
    83cc:	e92d4010 	push	{r4, lr}
    signal(SIGALRM, handler);
    83d0:	e59f102c 	ldr	r1, [pc, #44]	; 8404 <main+0x38>
    83d4:	e3a0000e 	mov	r0, #14	; 0xe
    83d8:	ebffffc5 	bl	82f4 <_init+0x38>
    alarm(1);  // 1秒后发送SIGALRM信号
    83dc:	e3a00001 	mov	r0, #1	; 0x1
    83e0:	ebffffc9 	bl	830c <_init+0x50>
    83e4:	e59f201c 	ldr	r2, [pc, #28]	; 8408 <main+0x3c>

    while(!flag) {
    83e8:	e5923000 	ldr	r3, [r2]			; 每次循环都重新读取flag
    83ec:	e3530000 	cmp	r3, #0	; 0x0		; 如果≠0跳转到退出
    83f0:	0afffffc 	beq	83e8 <main+0x1c>	; 继续循环
        // 空循环等待flag变化
    }

    printf("Main loop detected flag change\n");
    83f4:	e59f0010 	ldr	r0, [pc, #16]	; 840c <main+0x40>
    83f8:	ebffffc6 	bl	8318 <_init+0x5c>
    return 0;
}
......

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

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

相关文章

idea中提高编译速度研究

探索过程&#xff1a; 有三种情况&#xff1a; 第一种&#xff1a; idea中用eclipse编译器编译springboot项目&#xff0c;然后debug启动Application报错找不到类。 有待继续研究。 第二种&#xff1a; idea中用javac编译器编译springboot项目&#xff0c;重新构建用时&a…

静态链接part2

编译 语义分析 由语义分析器完成&#xff0c;这个步骤只是完成了对表达式的语法层面的分析&#xff0c;它并不了解这个语句是否真的有意义&#xff08;例如在C语言中两个指针做乘法运算&#xff0c;这个语句在语法上是合法的&#xff0c;但是没有什么意义&#xff1b;还有同样…

Vue3+Vite+TypeScript+Element Plus开发-17.Tags-组件构建

系列文档目录 Vue3ViteTypeScript安装 Element Plus安装与配置 主页设计与router配置 静态菜单设计 Pinia引入 Header响应式菜单缩展 Mockjs引用与Axios封装 登录设计 登录成功跳转主页 多用户动态加载菜单 Pinia持久化 动态路由 -动态增加路由 动态路由-动态删除…

3D语义地图中的全局路径规划!iPPD:基于3D语义地图的指令引导路径规划视觉语言导航

作者&#xff1a; Zehao Wang, Mingxiao Li, Minye Wu, Marie-Francine Moens, Tinne Tuytelaars 单位&#xff1a;鲁汶大学电气工程系&#xff0c;鲁汶大学计算机科学系 论文标题&#xff1a; Instruction-guided path planning with 3D semantic maps for vision-language …

ShellScript脚本编程

语法基础 脚本结构 我们先从这个小demo程序来窥探一下我们shell脚本的程序结构 #!/bin/bash# 注释信息echo_str"hello world"test(){echo $echo_str }test echo_str 首先我们可以通过文本编辑器(在这里我们使用linux自带文本编辑神器vim)&#xff0c;新建一个文件…

【HarmonyOS 5】敏感信息本地存储详解

【HarmonyOS 5】敏感信息本地存储详解 前言 鸿蒙其实自身已经通过多层次的安全机制&#xff0c;确保用户敏感信息本地存储安全。不过再此基础上&#xff0c;用户敏感信息一般三方应用还需要再进行加密存储。 本文章会从鸿蒙自身的安全机制进行展开&#xff0c;最后再说明本地…

探索鸿蒙沉浸式:打造无界交互体验

一、鸿蒙沉浸式简介 在鸿蒙系统中&#xff0c;沉浸式是一种极具特色的设计理念&#xff0c;它致力于让用户在使用应用时能够全身心投入到内容本身&#xff0c;而尽可能减少被系统界面元素的干扰。通常来说&#xff0c;就是将应用的内容区巧妙地延伸到状态栏和导航栏所在的界面…

网站301搬家后谷歌一直不收录新页面怎么办?

当网站因更换域名或架构调整启用301重定向后&#xff0c;许多站长发现谷歌迟迟不收录新页面&#xff0c;甚至流量大幅下滑。 例如&#xff0c;301跳转设置错误可能导致权重传递失效&#xff0c;而新站内容与原站高度重复则可能被谷歌判定为“低价值页面”。 即使技术层面无误&a…

在Mac上离线安装k3s

目录 首先是安装multipass。 1. 系统要求 2. 环境准备 本来想照着网上文档学习安装一下k3s&#xff0c;没想到在docker被封了之后&#xff0c;现在想通过命令行去下载github的资源也不行了&#xff08;如果有网友看到这个文档、并且知道问题原因的&#xff0c;请留言告知&am…

2025低代码平台选型策略:ROI导向下的功能与成本权衡

在当今快速变化的商业环境中&#xff0c;企业面临着前所未有的挑战与机遇。数字化转型已成为企业提升竞争力的关键&#xff0c;而软件开发的高成本和长周期无疑是实现这一转型的绊脚石。 低代码平台的兴起&#xff0c;为企业提供了一种高效、灵活的解决方案&#xff0c;使得非…

Redis的IO多路复用

1 传统的socket编码模型 传统 Socket 模型通常采用 多线程/多进程 或 阻塞 I/O 的方式处理网络请求。以下是典型实现步骤&#xff1a; 创建套接字&#xff08;Socket&#xff09; 步骤&#xff1a;调用 socket() 创建一个 TCP/UDP 套接字。通常把这个套接字称为【主动套接字】…

基于YOLOv9的课堂行为检测系统

基于YOLOv9的课堂行为检测系统 项目概述 本项目是一个基于YOLOv9深度学习模型的课堂行为检测系统&#xff0c;旨在通过计算机视觉技术自动识别和监测课堂中学生的各种行为状态&#xff0c;帮助教师更好地了解课堂教学效果。 项目结构 课堂行为检测/ ├── data/ │ ├──…

端、管、云一体化原生安全架构 告别外挂式防护!

面对数字化转型浪潮&#xff0c;企业网络安全风险日益凸显。数据泄露、黑客勒索等事件频发&#xff0c;合规要求加速推进。尽管企业纷纷部署了防病毒、身份认证、文件加密、入侵防护、流量监控等多种安全系统&#xff0c;但分散且孤立的架构非但没有有效抵御风险&#xff0c;反…

BI面向模型开发和面向报表开发,有什么区别?

在数字化时代&#xff0c;商业智能&#xff08;BI&#xff09;已成为企业决策不可或缺的工具。BI项目实施时&#xff0c;通常有两种开发模式&#xff1a;面向模型开发和面向报表开发。虽然两者都旨在通过数据驱动决策&#xff0c;但在开发逻辑、目标价值和技术路径上存在显著差…

进程控制(上)【Linux操作系统】

进程控制 写时拷贝 本质是一种减少深拷贝的方法 Linux中有很多拷贝的场景都用得上写时拷贝&#xff0c;下面以创建子进程时的写时拷贝为例&#xff1a; 子进程被创建的时候&#xff1a; 会继承父进程的mm_struct和页表 所以子进程刚刚继承时&#xff0c;父子进程的代码和数据…

5G网络下客户端数据业务掉线频繁

上层应用的日志和界面在待机状态下&#xff08;即没有做通话等业务操作&#xff09;&#xff0c;会频繁提示“离线”。 主要先看有没有丢网&#xff0c;UL BLER有没有问题。确认没有问题。看到业务信道释放后也可以成功重新建链。所以以为这个只是终端业务进入dormant态的提示…

【Docker项目实战】使用Docker部署Gitblit服务器

【Docker项目实战】使用Docker部署Gitblit服务器 一、Gitblit介绍1.1 Gitblit 介绍1.2 主要特点 二、本次实践规划2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载Gitblit镜像五、部署Gitbli…

Vitis: 使用自定义IP时 Makefile错误 导致编译报错

参考文章: 【小梅哥FPGA】 Vitis开发中自定义IP的Makefile路径问题解决方案 Vitis IDE自定义IP Makefile错误&#xff08;arm-xilinx-eabi-gcc.exe: error: *.c: Invalid argument&#xff09;解决方法 Vitis 使用自定义IP时: Makefile 文件里的语句是需要修改的&#xff0c;…

helm的go模板语法学习

1、helm chart 1.0、什么是helm&#xff1f; 介绍&#xff1a;就是个包管理器。理解为java的maven、linux的yum就好。 安装方法也可参见官网&#xff1a; https://helm.sh/docs/intro/install 通过前面的演示我们知道&#xff0c;有了helm之后应用的安装、升级、查看、停止都…

AI 语音公司 ElevenLabs 进军亚太市场设立东京子公司;EverTutor Live :语音交互 AI 教育平台丨日报

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real-Time Engagement&#xff09; 领域内「有话题的 技术 」、「有亮点的 产品 」、「有思考的 文章 」、「有态度的 观…