C 語言中的 volatile 關鍵字

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	; 0xe83d8:	ebffffc5 	bl	82f4 <_init+0x38>alarm(1);  // 1秒后發送SIGALRM信號83dc:	e3a00001 	mov	r0, #1	; 0x183e0:	ebffffc9 	bl	830c <_init+0x50>83e4:	e59f3020 	ldr	r3, [pc, #32]	; 840c <main+0x40>83e8:	e5932000 	ldr	r2, [r3]		      ; 從內存讀取 flag 值到 r283ec:	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	; 0xe83d8:	ebffffc5 	bl	82f4 <_init+0x38>alarm(1);  // 1秒后發送SIGALRM信號83dc:	e3a00001 	mov	r0, #1	; 0x183e0:	ebffffc9 	bl	830c <_init+0x50>83e4:	e59f201c 	ldr	r2, [pc, #28]	; 8408 <main+0x3c>while(!flag) {83e8:	e5923000 	ldr	r3, [r2]			; 每次循環都重新讀取flag83ec:	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.pswp.cn/diannao/79216.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/79216.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/79216.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java 洛谷題單【算法2-1】前綴和、差分與離散化

P8218 【深進1.例1】求區間和 解題思路 前綴和數組&#xff1a; prefixSum[i] 表示數組 a 的前 (i) 項的和。通過 prefixSum[r] - prefixSum[l - 1] 可以快速計算區間 ([l, r]) 的和。 時間復雜度&#xff1a; 構建前綴和數組的時間復雜度是 (O(n))。每次查詢的時間復雜度是 …

綠盟二面面試題

5000篇網安資料庫https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39a6eab17cc0ed0fca5f0e4c979ce64bd112762def9ee7cf0112a7e76af&scene21#wechat_redirect 1. 原理深度&…

線程安全學習

1 什么是線程 線程是cpu調度的最小單位&#xff0c;在Linux 下 實現線程的方式為輕量級進程&#xff0c;復用進程的結構體&#xff0c;使用clone函數創建 2 線程安全 所謂線程安全&#xff0c;更確切的應該描述為內存安全 #include <stdio.h> #include <pthread.h…

Linux紅帽:RHCSA認證知識講解(十 三)在serverb上破解root密碼

Linux紅帽&#xff1a;RHCSA認證知識講解&#xff08;十 三&#xff09;在serverb上破解root密碼 前言操作步驟 前言 在紅帽 Linux 系統的管理工作中&#xff0c;系統管理員可能會遇到需要重置 root 密碼的情況。本文將詳細介紹如何通過救援模式進入系統并重新設置 root 密碼。…

**Microsoft Certified Professional(MCP)** 認證考試

1. MCP 認證考試概述 MCP&#xff08;Microsoft Certified Professional&#xff09;是微軟認證體系中的一項入門級認證&#xff0c;旨在驗證考生在微軟產品和技術&#xff08;如 Windows Server、Azure、SQL Server、Microsoft 365&#xff09;方面的技能。2020 年&#xff0…

系統性能優化總結與思考-第一部分

1.C代碼優化策略總結 編譯器方面&#xff1a;用好的編譯器并用好編譯器&#xff08;支持C11的編譯器&#xff0c;IntelC&#xff08;速度最快&#xff09;GNU的C編譯器GCC/G&#xff08;非常符合標準&#xff09;&#xff0c;Visual C&#xff08;性能折中&#xff09;&#x…

RCL諧振電壓增益曲線

諧振電路如何通過調頻實現穩壓&#xff1f; 為什么要做諧振&#xff1f; 在諧振狀態實現ZVS導通&#xff0c;小電流關斷 電壓增益GVo/Vin&#xff0c;相當于產出投入比 當ff0時&#xff0c;G1時&#xff0c;輸出電壓輸入電壓 當G<1時&#xff0c;輸出電壓<輸入電壓 …

Linux進程相關選擇題及解析

1. 關于Linux進程創建,以下說法正確的是? A. fork()函數調用后,子進程從父進程的fork()之后開始執行 B. fork()函數返回兩次,父進程返回子進程PID,子進程返回0[10][11] C. exec函數族會替換當前進程的代碼段,但保留數據段和堆棧 D. wait()函數只能等待直接子進程退出 答…

STM32 HAL DHT11驅動程序

DHT11驅動程序會占用TIM3定時器&#xff0c;進行高精度延時。程序共包含4個文件 DHT11.c DHT11.h delay.c delay.h DHT11.c #include "stm32f1xx_hal.h" #include "dht11.h" #include "delay.h" // 添加延時頭文件 #define DHT_PORT GPIOB…

網頁防篡改與盜鏈防護:實時監控與自動化修復實踐

摘要&#xff1a;針對網頁內容篡改與盜鏈問題&#xff0c;本文基于群聯AI云防護系統&#xff0c;詳解如何通過哈希校驗、實時監控與CDN聯動實現秒級修復&#xff0c;并提供Python與AWS S3集成代碼。 一、網頁安全的核心需求 防篡改&#xff1a;保障頁面內容完整性&#xff0c;…

【4】k8s集群管理系列--harbor鏡像倉庫本地化搭建

一、harbor基本概念 ?Harbor是一個由VMware開源的企業級Docker鏡像倉庫解決方案?&#xff0c;旨在解決企業在容器化應用部署中的痛點&#xff0c;提供鏡像存儲、管理、安全和分發的全生命周期管理?。Harbor擴展了Docker Registry&#xff0c;增加了企業級功能&#xff0c;如…

Docker 安裝 Elasticsearch 8.x

Docker 安裝 Elasticsearch 8.x 前言一、準備工作二、設置容器的目錄結構三、啟動一個臨時的容器來復制配置文件四、復制配置文件到本地目錄五、刪除臨時容器六、創建并運行容器&#xff0c;掛載本地目錄七、修改文件配置監聽端口八、端口配置&#xff1a;Host 網絡模式 vs Por…

C#: 用Libreoffice實現Word文件轉PDF

現實場景中要實現Word格式轉PDF格式還是比較常見的。 如果要用開源的組件&#xff0c;只有用Libreoffice了。 一、下載安裝Libreoffice 先進入如下鏈接&#xff0c;找到最新版本和匹配的操作系統來安裝。 官網試過&#xff0c;下載是能下載&#xff0c;但安裝了用不了&…

MoogDB數據庫日常維護技巧與常見問題解析

在當今的數據驅動世界中&#xff0c;數據庫作為信息存儲與管理的核心組件&#xff0c;扮演著舉足輕重的角色。MoogDB作為一款高性能、易擴展的數據庫解決方案&#xff0c;越來越受到開發者和企業的青睞。為了確保MoogDB的穩定性與高性能&#xff0c;定期的日常維護及對常見問題…

JAVA多線程的幾種實現方式

?1. 繼承 Thread 類? ?原理?&#xff1a;通過繼承 Thread 類并重寫 run() 方法定義線程任務&#xff0c;調用 start() 啟動線程?。?代碼示例?&#xff1a; public class MyThread extends Thread {Overridepublic void run() {System.out.println("線程 " g…

爬蟲(基本知識介紹,urllib庫的說明)

爬蟲 爬蟲基礎&#xff08;一些基本原理的梳理&#xff09; scheme://[username:password]hostname[:port][/path][;parameters][?query][#fragment] 注&#xff1a; parameters 和 query 混用&#xff0c;并且現在 query 用的多 ?query 查詢 &#xff0c;用來查詢某類資源…

探秘串口服務器廠家:背后的故事與應用

在科技飛速發展的今天&#xff0c;串口服務器作為連接串口設備與網絡的橋梁&#xff0c;在工業自動化、智能交通、智能家居等眾多領域發揮著關鍵作用。你是否好奇&#xff0c;那些生產串口服務器的廠家究竟有著怎樣的故事&#xff1f;它們的產品背后又蘊含著怎樣的原理呢&#…

工廠能耗系統智能化解決方案 —— 安科瑞企業能源管控平臺

安科瑞顧強 政策背景與“雙碳”戰略驅動 2025年《政府工作報告》明確提出“單位國內生產總值能耗降低3%左右”的目標&#xff0c;要求通過產業結構升級&#xff08;如高耗能行業技術革新或轉型&#xff09;、能源結構優化&#xff08;提高非化石能源占比&#xff09;及數字化…

BI面向模型開發和面向報表開發,有什么區別?

在數字化時代&#xff0c;商業智能&#xff08;BI&#xff09;已成為企業決策不可或缺的工具。BI項目實施時&#xff0c;通常有兩種開發模式&#xff1a;面向模型開發和面向報表開發。雖然兩者都旨在通過數據驅動決策&#xff0c;但在開發邏輯、目標價值和技術路徑上存在顯著差…

OpenHarmony人才認證證書

OpenHarmony人才認證體系目前支持初級工程師認證&#xff0c;要求了解OpenHarmony開源項目、生態進展及系統移植等基礎知識&#xff0c;熟練掌握OpenHarmony的ArkUI、分布式軟總線、分布式硬件、分布式數據管理等基礎能力使用&#xff0c;具備基礎的開發能力。 考試流程可參考O…