性无码一区二区三区在线观看,少妇被爽到高潮在线观看,午夜精品一区二区三区,无码中文字幕人妻在线一区二区三区,无码精品国产一区二区三区免费

徐土豆
認(rèn)證:優(yōu)質(zhì)創(chuàng)作者
所在專題目錄 查看專題
C語言中內(nèi)循環(huán)和外循環(huán)的位置可能產(chǎn)生性能上的區(qū)別
[C語言朝花夕拾] C語言中的命令行輸入?yún)?shù)判斷
用“位操作”取代“取模操作”判斷奇數(shù)偶數(shù)
c語言運行時出現(xiàn)segment fault的原因
一文理解C語言中的volatile修飾符
C語言中的內(nèi)存布局(memory layout)
作者動態(tài) 更多
給定計算預(yù)算下的最佳LLM模型尺寸與預(yù)訓(xùn)練數(shù)據(jù)量分配
05-19 09:33
大模型推理時的尺度擴(kuò)展定律
05-18 10:32
世界多胞體與世界模型
05-13 09:42
獎勵模型中的尺度擴(kuò)展定律和獎勵劫持
05-12 08:41
MeCo——給預(yù)訓(xùn)練數(shù)據(jù)增加源信息,就能減少33%的訓(xùn)練量并且提升效果
05-08 09:13

一文理解C語言中的volatile修飾符

本文轉(zhuǎn)自徐飛翔的“一文理解C語言中的volatile修飾符

版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。

前言

volatile修飾符是在嵌入式開發(fā)和多線程并發(fā)編程中常見的修飾符,理解其對于實踐過程非常有幫助,此文參考了[1],并且附上了筆者的一些例子,希望對大家有所幫助。

volatile修飾符用于C語言和C++中,其意在阻止編譯器對其修飾對象進(jìn)行任何形式的優(yōu)化,有時候,這種編譯器“自作主張”的優(yōu)化會導(dǎo)致編程者意想不到的結(jié)果,因此需要引入這個關(guān)鍵字進(jìn)行限制。

當(dāng)一個對象可能會被當(dāng)前代碼以外的環(huán)境,在任何時刻被改變的時候,一個對象如果此時被聲明為了volatile,那么其就可以脫離編譯器的優(yōu)化過程。當(dāng)需要讀取該數(shù)據(jù)的時候,系統(tǒng)總是會重新從內(nèi)存位置中讀取當(dāng)前volatile類型的數(shù)據(jù),而不是直接取其在寄存器中的值,我們要知道,為了執(zhí)行效率,即便是你指定了要從之前的同一個對象中取值,編譯器在優(yōu)化過程中也很可能會不直接從內(nèi)存中讀取數(shù)據(jù),而是直接采用寄存器中的值(我們將會從后續(xù)的例子中看到這個情況。),這個行為在一般情況下的確能夠提高程序的執(zhí)行速率,畢竟數(shù)據(jù)從寄存器中讀取,要比從內(nèi)存中讀取快上好幾個數(shù)量級。比如我們見一個簡單例子:

// sample.c
int main(){
    int i = 10;
    i = i;
    return 0;
}

在linux下用命令gcc -S sample.c編譯,我們得到了其匯編結(jié)果,我截取了其主體,如:

movl  $10, -4(%rbp)

而在sample.c的變量聲明中如果加上volatile修飾符,那么程序變成:

// sample.c
int main(){
    volatile int i = 10;
    i = i;
    return 0;
}

匯編后的結(jié)果為:

movl  $10, -4(%rbp)
movl  -4(%rbp), %eax
movl  %eax, -4(%rbp)

我們對比這兩次的匯編結(jié)果,我們發(fā)現(xiàn),第一次沒有聲明其為易變的時候,編譯器分析了代碼的變量的關(guān)系,并且進(jìn)行了優(yōu)化,編譯器認(rèn)為,我的變量i既然在下一步還需要賦值給自己,那么何必在重新從內(nèi)存中讀取i的值呢,因此,從匯編來看,i = i這條c語言代碼其實是無效的。 在一般的編譯器優(yōu)化中,因為編譯器可能會認(rèn)為變量i是非易變的,如果其變化了,只能是因為程序員對其進(jìn)行了顯式的賦值改變,因此在需要再次讀取變量i的值的時候,與其重新從內(nèi)存中讀取,不如直接利用其已經(jīng)讀入到寄存器中的值,畢竟寄存器比內(nèi)存快得多。

但是我們要思考下,i = i;是不是沒有意義的代碼呢?我們很容易認(rèn)為這個答案 是的確沒有意義。

但是,我們假設(shè)有一種情況,在int i = 10;之后,因為某種原因,比如硬件中斷,多線程的修改或者其他原因,導(dǎo)致此時i改變了,而不是初始的i = 10了,那么我們后續(xù)的代碼i = i;就變得非常重要,因為其需要讀取在內(nèi)存中,新的值i,而不是簡單的將其忽視掉或者簡單地讀取內(nèi)存中的值,注意到這個時候寄存器中的值已經(jīng)是“過時”了的,如果任由編譯器去優(yōu)化,那么你將永遠(yuǎn)無法讀取傳感器的值(傳感器的值很多由硬件中斷讀取。)

通過上面的討論,我們便能理解這兩個不同的匯編結(jié)果了,在第二段匯編中,我們不僅通過movl $10, -4(%rbp)將直接數(shù)10傳輸?shù)搅藘?nèi)存-4(%rbp) (指的是寄存器%rbp中的地址所指向的內(nèi)存偏移4個字節(jié)的內(nèi)存位置,是相對尋址的指令),而且接下來還重新讀取了該內(nèi)存位置的值,并且將其賦給了自己的這個內(nèi)存位置(這個過程中,因為該變量可能是易變的,因此該內(nèi)存可能會被其他程序給覆蓋,因此要重新讀?。?/p>

重新回到我們的討論,那么什么時候我們需要用volatile這個修飾符呢?當(dāng)屬于下面幾種情況的時候,應(yīng)該考慮這個修飾符:

  1. 當(dāng)全局變量會被中斷服務(wù)函數(shù)給修改的時候。例如一個全局變量可以表示一個外部數(shù)據(jù)接口(通常全局指針被引用為內(nèi)存映射IO),這意味著該數(shù)據(jù)會被動態(tài)地更新。如果我們的代碼期望讀取數(shù)據(jù)接口的值,那么我們就應(yīng)該將其定義為volatile,以獲取其數(shù)據(jù)的最新值。如果我們不這么做,編譯器的優(yōu)化過程會使得只讀取一次該接口的數(shù)據(jù),并且將其加載到寄存器中,接下來都只能讀取該寄存器中的舊值了。
  2. 在多線程應(yīng)用中的全局變量。在多線程通信中,有著多種通信方式:信號傳遞(message passing),郵箱(mail boxes),共享內(nèi)存(shared memory)等。一個全局變量是共享內(nèi)存的樸素形式。當(dāng)兩個線程通過全局變量共享信息時,他們需要用volatile進(jìn)行修飾。因為線程是異步運行的,每個線程導(dǎo)致的全局變量的每次更新,都應(yīng)該被其他線程重新從內(nèi)存中獲取。為了消除編譯器優(yōu)化導(dǎo)致的效果,這些全局變量必須要用volatile修飾。

如果我們不用volatile修飾,有可能會導(dǎo)致以下問題:

  1. 當(dāng)編譯器優(yōu)化開啟時,代碼可能不會正常工作。
  2. 當(dāng)中斷發(fā)生時,代碼可能不會正常工作。

Reference

[1]. https://www.geeksforgeeks.org/understanding-volatile-qualifier-in-c/

聲明:本內(nèi)容為作者獨立觀點,不代表電子星球立場。未經(jīng)允許不得轉(zhuǎn)載。授權(quán)事宜與稿件投訴,請聯(lián)系:editor@netbroad.com
覺得內(nèi)容不錯的朋友,別忘了一鍵三連哦!
贊 4
收藏 6
關(guān)注 52
成為作者 賺取收益
全部留言
0/200
成為第一個和作者交流的人吧