0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

C語言中的volatile是什么

jf_78858299 ? 來源:碼農(nóng)的荒島求生 ? 作者:碼農(nóng)的荒島求生 ? 2023-02-17 14:29 ? 次閱讀

學(xué)C語言時有一個奇怪的關(guān)鍵字volatile,這到底有什么用呢?

volatile與編譯器

首先來看這樣一段代碼:

int busy = 1;
void wait() { while(busy) { ; }}

編譯一下,注意, 這里使用O2優(yōu)化

圖片

讓我們仔細(xì)看看生成的這段匯編

wait:        mov     eax, DWORD PTR busy[rip].L2:        test    eax, eax        jne     .L2        retbusy:        .long   1

其中L2這一段即為while循環(huán),這段指令是經(jīng)過編譯器優(yōu)化的,可以看到,決定能否跳出循環(huán)是通過檢查寄存器eax來完成的,而沒有檢查變量busy所在內(nèi)存的真實內(nèi)容。

注意,對于這段代碼來說這里的優(yōu)化是正確的,但問題是如果還有其它代碼修改了變量busy,那么這里的優(yōu)化會導(dǎo)致其它代碼對變量busy的修改根本就不能生效,就像這樣:

int busy = 1;
// 該函數(shù)在A線程中執(zhí)行void wait() { while(busy) { ; }}
// 該函數(shù)在B線程中執(zhí)行void signal() { busy = 0;}

如果wait函數(shù)中while循環(huán)對應(yīng)的機器指令僅僅從寄存器中讀取數(shù)據(jù)那么即使B線程的signal函數(shù)修改了busy變量也不能讓wait函數(shù)從循環(huán)中跳出來。

如果你對busy變量使用volatile修飾,生成的指令就變成這樣了:

圖片

wait:.L2:        mov     eax, DWORD PTR busy[rip]        test    eax, eax        jne     .L2        retbusy:        .long   1

注意看此時L2這一段,每次都從busy變量所在的內(nèi)存中讀取數(shù)據(jù)并存放在eax,然后再去判斷,這樣就能確保每次都能讀取到busy變量的最新值。

實際上你可以把寄存器eax當(dāng)做busy所在內(nèi)存的cache,當(dāng)cache(寄存器)和內(nèi)存中的數(shù)據(jù)一致時不會有任何問題,但當(dāng)cache與內(nèi)存中的數(shù)據(jù)不一致時(也就是內(nèi)存已被更新但cache保存的還是舊數(shù)據(jù)),程序的運行往往出乎預(yù)料。

除了多線程的例子,還有一類就是signal handler以及硬件修改該變量(用C語言與硬件交互式時經(jīng)常遇到),如果編譯器生成文章開頭那樣的指令那么等待線程將檢測不到signal handler或者硬件對變量的修改。

圖片

因此在這里我們需要告訴編譯器:“不要耍小聰明,不要只從寄存器中讀數(shù)據(jù),這個變量可能在其它地方已經(jīng)被修改了,使用時從內(nèi)存中獲取最新數(shù)據(jù)”。

現(xiàn)在是時候簡單總結(jié)一下了, volatile僅僅阻止編譯器試圖去優(yōu)化對變量的讀取操作 。

volatile與多線程

一定要注意volatile僅僅確保變量的可見性,但和變量的原子訪問沒有半毛錢關(guān)系,這是兩個完全不同的任務(wù) 。

假設(shè)有一個非常復(fù)雜的結(jié)構(gòu)體struct foo:

struct data {  int a;  int b;  int c;  ...};
volatile struct data foo;
void thread1() { foo.a = 1; foo.b = 2; foo.c = 3; ...}
void thread2() { int a = foo.a; int b = foo.b; int c = foo.c; ...}

你僅僅用volatile去修飾變量foo只是確保了當(dāng)該變量被thread1修改后我們能在thread2中讀取到最新值, 但是這解決不了多線程并發(fā)讀寫需要原子訪問foo的問題 。

確保變量原子性訪問一般都采用鎖,當(dāng)使用鎖時,鎖本身就包含了volatile提供能力,即,確保變量的可見性,因此當(dāng)使用鎖時沒有必要使用volatile。

volatile與memory order

有的同學(xué)可能會想如果我想用volatile修飾的變量沒有那么復(fù)雜,僅僅是一個int,就像這樣:

volatile int busy = 0;

A線程讀取busy變量,B線程更新busy變量,當(dāng)A檢測到busy變化后執(zhí)行特定操作,這樣可行嗎?既然通過volatile修飾后可以確保每次都從內(nèi)存中讀取busy,那么應(yīng)該可以這樣使用吧。

然而,計算機在概念上可能相對簡單些,但在工程實踐中是復(fù)雜的。

我們知道由于CPU與內(nèi)存之間的速度差異非常大,CPU與內(nèi)存之間有一層cache,CPU其實并沒有直接讀取內(nèi)存,cache的存在會讓問題復(fù)雜起來,限于篇幅與本文主題這里不再展開。

為優(yōu)化內(nèi)存讀寫,CPU可能會對內(nèi)存讀寫操作進(jìn)行指令重排,reordering,帶來的后果就是:假設(shè)在線程1中先后執(zhí)行第N行代碼與第N+1行代碼,但在線程2看來卻是第N+1行代碼先生效,假設(shè)X的初始值為0,Y的初始值為1:

線程1           線程2
X = 10         if (!busy)
busy = 0;         Y = X;

當(dāng)線程2檢測到busy為0后讀取X的值,此時讀取到的X值可能為0。

為解決這一問題,我們需要的不是volatile,volatile解決不了reordering問題,我們需要的是內(nèi)存屏障,memory barrier。

內(nèi)存屏障是一類機器指令,該指令對處理器在該屏障指令之前與之后的內(nèi)存操作進(jìn)行了限制,確保不會出現(xiàn)重排問題。

而內(nèi)存屏障帶來的效果依然能夠涵蓋volatile提供的功能,因此也不需要volatile。

可以看到,在多線程環(huán)境下我們幾乎總是不會使用volatile關(guān)鍵字。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • C語言
    +關(guān)注

    關(guān)注

    180

    文章

    7575

    瀏覽量

    134016
  • 匯編
    +關(guān)注

    關(guān)注

    2

    文章

    214

    瀏覽量

    25834
  • volatile
    +關(guān)注

    關(guān)注

    0

    文章

    44

    瀏覽量

    12979
收藏 人收藏

    評論

    相關(guān)推薦

    C語言關(guān)鍵字volatile的用法

    許多程序員都無法正確理解C語言關(guān)鍵字volatile,這并不奇怪。因為大多數(shù)C語言書籍通常都是一兩句一帶而過,本文將告訴你如何正確使用它。
    發(fā)表于 04-11 09:35 ?2381次閱讀

    c語言中 volatile _Bool 關(guān)鍵字說明

    這個帖子送給沒有太多實踐經(jīng)驗的人,如果是大神,請?zhí)^首先 _Bool 是C99新增的一個,用法和bool一樣, 如下圖: 雖然故意初始化為2,但是下邊變量顯示的值為1;然后為什么我要用
    發(fā)表于 01-06 10:46

    請問C語言中volatile有的作用是什么?

    C語言中volatile有什么用作用是什么?看到一個數(shù)組,別人這樣寫:volatile unsigned short adc_dma_buf[40];u32 ADCValue_ActC
    發(fā)表于 10-23 22:16

    STM32CubeMX的相關(guān)資料分享

    鏈接5中的代碼)1.C語言中volatile關(guān)鍵字的學(xué)習(xí)2.【STM32】HAL庫 STM32CubeMX教程十一—DMA (串口DMA發(fā)送接收)3.C
    發(fā)表于 01-14 07:10

    c語言中volatile關(guān)鍵字作用

    一個定義為volatile 的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設(shè)這個變量的值了。精確地說就是,優(yōu)化器在用到這個變量時必須每次都小心地重新讀
    發(fā)表于 06-22 17:04 ?46次下載

    C語言中volatile關(guān)鍵字

    volatile關(guān)鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改。
    發(fā)表于 05-27 09:32 ?3245次閱讀

    C語言中指針的介紹非常詳細(xì)

    C語言中指針的介紹非常詳細(xì) C語言中指針的介紹非常詳細(xì)
    發(fā)表于 12-25 10:39 ?57次下載

    C語言和匯編語言混合編程方法和C語言中斷處理方法

    C語言和匯編語言混合編程方法和C語言中斷處理方法,new
    發(fā)表于 01-06 14:36 ?36次下載

    C語言中的關(guān)鍵字

    C語言中的入門教程
    發(fā)表于 10-14 16:24 ?3次下載

    C語言中volatile關(guān)鍵字的使用

    volatile 的意思是易變的、可變的,作用是限制編譯器優(yōu)化某些變量。首先看一段C51程序: Keil在優(yōu)化級別是為8時得到如下匯編代碼(部分未列出):可以看到,變量d的值賦給x,y,z時,只有x
    的頭像 發(fā)表于 03-17 11:55 ?1.3w次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言中</b><b class='flag-5'>volatile</b>關(guān)鍵字的使用

    C語言類型修飾符Volatile的使用說明

    C語言是我們經(jīng)常需要用到的語言C語言中的類型修飾符Volatile大家知道怎么使用嗎?
    的頭像 發(fā)表于 09-19 10:54 ?3455次閱讀

    總結(jié)那么幾個C語言中的“坑”

    總結(jié)幾個C語言中的“坑”
    的頭像 發(fā)表于 01-16 10:52 ?2476次閱讀

    【嵌入式】C語言中volatile關(guān)鍵字

    volatile06. 附錄01. volatile概述volatileC語言中的一個關(guān)鍵字。將變量定義為
    發(fā)表于 10-21 10:21 ?6次下載
    【嵌入式】<b class='flag-5'>C</b><b class='flag-5'>語言中</b><b class='flag-5'>volatile</b>關(guān)鍵字

    HAL庫STM32串口DMA不定長收發(fā),空閑中斷

    參考了鏈接5中的代碼)1.C語言中volatile關(guān)鍵字的學(xué)習(xí)2.【STM32】HAL庫 STM32CubeMX教程十一—DMA (串口DMA發(fā)送接收)3.C
    發(fā)表于 01-14 10:40 ?18次下載
    HAL庫STM32串口DMA不定長收發(fā),空閑中斷

    c#語言中怎么使用HTTP代理

    c#語言中怎么使用HTTP代理。
    的頭像 發(fā)表于 09-01 14:46 ?1988次閱讀