C 和 C 的 volatile 關鍵字為什麼給程式設計者造成了如此大的誤解?

時間 2021-05-12 05:02:17

1樓:望山

我看題主的誤解也不小。volatile關鍵字提出時候那個年代,記憶體發生CPU無法預知的意外變化的主要原因是這個位址對應的是外設暫存器,但並非只有這一種情況。多CPU或者多核心爭用記憶體,還有另外答主說的ISR裡面改了記憶體,也是屬於volatile需要發揮作用的情況。

這個關鍵字就是用來阻止編譯器優化的,根據標準不需要生成barrier,所以它不能「完全」解決多CPU或者多核心爭用記憶體的問題。但是(老師說但是後面最重要),在C++11之前哪個語言標準告訴你放置了barrier就能阻止編譯器把變數分配到暫存器裡去?在C++11裡恐怕也只有使用std::

atomic庫裡面的函式,編譯器才能「確信」你需要阻止暫存器優化。假如你用asm嵌入barrier指令,或者barrier深藏在函式呼叫裡(例如藏在作業系統的API裡),編譯器就未必有那麼聰明了。所以,對於多CPU爭用的記憶體,volatile和顯式同步機制都是需要的,缺一不可。

2樓:李建愁

哈哈哈一定是受了這篇文章類似觀點的影響吧。

倒不是說吧volatile當成atomic,而是為了pay extra to shoot your foot

volatile: The Multithreaded Programmer's Best Friend

3樓:gashero

與volatile修飾符對應的修飾符是register。

乙個函式在執行時會使用多個區域性變數,包括形參。這些變數使用頻度差異較大,編譯器會按照使用頻度來規劃,將常用變數放到閒置暫存器裡,使得運算速度得到很大的提高。不常用的那些就放記憶體裡,每次用到就去記憶體裡拿。

乙個值存放在暫存器和記憶體裡,訪問速度的差別可達數百倍甚至上千倍。可見將變數放在暫存器裡的意義還是很大的。

但對於可能被搶占的任務,乙個變數放在暫存器裡就成了問題。因為被搶占後,同乙個變數在記憶體裡和暫存器裡的值可能會不一樣。我這裡用了搶占這個詞,包括搶占式多工作業系統裡的多程序或多執行緒排程,也包括嵌入式開發裡的中斷,當然其實底層都是中斷。

對非搶占式多工則不存在這個問題,比如協程。

volatile的含義就是明確告訴編譯器,這個變數在每次訪問時,都走記憶體,而不要用暫存器來快取。這樣在搶占式多工裡,就能確保每次拿到最新的值。而register的意思則相反,告訴編譯器這個變數盡可能放到暫存器裡,以提高速度。

當然如果暫存器不夠用,那就還是放記憶體裡。

實際使用中,如果乙個變數僅在函式內使用,或作為值傳遞的形參,那麼適合使用register修飾符。而如果是全域性變數,且可能被多人任務讀寫,則應該明確的使用volatile。比如中斷中訪問的全域性變數,就應該用volatile。

作業系統課程沒仔細看的人,可能難以理解為何暫存器與記憶體裡的值會不同。如下說一下中斷發生時,CPU的動作。

中斷發生時,CPU會立即把當前所有暫存器的值存入任務的自己的記憶體區域裡。空出所有暫存器,交給中斷去做事。中斷處理函式返回後,CPU再把需要喚醒的任務的記憶體區里暫存器部分內容一一載入到暫存器裡,並跳轉到上次中斷的位址,傳入PC指標。

這樣任務就可以繼續執行了。這裡的關鍵就是中斷結束後恢復到暫存器的值是從任務私有記憶體區載入的,而不是從原始變數載入的。所以中斷期間對變數的修改就無法立即反應到暫存器裡。

搶占式多工的具體實現也是利用了中斷。在中斷處理函式中協調任務優先順序和呼叫,原理同上。

4樓:陳小波

也許是因為現在搞技術的人越來越缺乏嚴謹治學精神,太多的想當然和懶惰,學技術不是因為熱忱與喜愛,只是餬口的工具,好像沒什麼錯哦,可是有句古話,幹一行愛一行,也是對自己的負責。

5樓:

這個問題跟之前乙個問題一脈相承:多執行緒程式設計中什麼情況下需要加 volatile?

其實也不能說是什麼誤解,因為命令式語言語義上來說,一般自然假定程式語句是順序執行的。就算考慮到編譯器翻譯過程,那麼volatile剛好又能阻止編譯器優化,那麼看起來也就沒啥問題了,再加上最流行的X86是強序模型的,的確保證了一些簡單實現的正確性。

所以從古老的編譯器和X86時代起步的,的確不會在這個問題栽什麼跟頭,這也是為什麼後來人要強調不能用volatile的原因。如果一開始大家遇到的都是亂序的CPU,也就自然不會這麼去想了。

6樓:Alinshans

同樣的還有 **inline**。

放到20年前談內聯優化,也沒什麼。

可是就在如今,前幾天C++課上我們老師還一直在講「內聯」,「優化」,「膨脹」,對 inline 真實的語義完全沒有提及,我也很失望啊。

7樓:

這個鍋得微軟背了.msvc的volatile就是帶一些原子語義,行為方式和c#的volatile一樣,.在沒有std::

atomic的時候volatile就是起到一定的std::atomic的作用.

當然volatile相對atomic還是有很多不同.比如update就不保護。

然後一些答主擔心的其他核心的髒讀問題,除非用的上古cpu不然是不存在的。cache一致協議會解決問題。

8樓:

volatile在單核機器上確實有這個功能的,可能以前的cpu是單核的吧。volatile的意思是要重新從記憶體中讀取這個變數,單核時,如果有顯示指令重新從記憶體load,那麼資料一定是對的。但是在多核時,這個資料可能就是錯的,就算顯式的有指令從記憶體load,也可能cpu0(隨便取的乙個cpu)只是從cache中load這個資料,如果不久前cpu1恰好修改了這個資料,那麼現在cpu0拿到這個資料就是錯的,而且就算cpu0確實是到記憶體中去拿這個資料,也可能出錯,因為cpu1修改的資料可能還沒寫回記憶體。

真多核機上,多執行緒環境下,要原子的操作某執行緒間共享記憶體,要在指令前加lock字首的,具體原理我也不是很清楚,好像是鎖匯流排,然後在cpu間廣播這個修改(不對請指正)。所以原子操作對cache的破壞是非常大的,盡量使用執行緒本地變數,鎖爭用時會造成大量cachemiss應該是這個原因,鎖爭用時,多個執行緒同時cas乙個共享變數,多次造成cpu間的資料廣播/cache miss。

最後c/cpp中的原子操作,在linux上gcc提供了 __sync*的一系列函式

9樓:dorianzheng

volatile在x86和x64上確實可以起到記憶體屏障的作用,因為這兩個平台的cpu都是強記憶體模型,volatile同時可以組織編譯器做指令重排。另外一些基本型別例如整型和指標在這兩個平台上的操作本身就是原子化的

難道我沒說清楚?因為x86本身是強記憶體模型,所以本身就可以保證acqire和release語意,acquire對應loadstore和storestore記憶體屏障,release對應loadload和loadstore記憶體屏障,加上volatile可以阻止編譯器優化,所以volatile在x86上確實可以正確實現記憶體屏障啊

懶得解釋了,祭出preshing大神文章:Acquire and Release Semantics

不管放出多少篇文章都沒人看的樣子:http://gcc.gnu.org/ml/gcc/2003-04/msg01180.html

C 中,async關鍵字到底起什麼作用?

小墨 async 就把他當成乙個非同步的標誌就好啦。就當做和void 一樣。await 等待非同步 但也可以使用xx.result 獲取 await之後的結果。 pokemon 高階篇 以IL為劍,直指async await 布魯克石 CSDN部落格 可以看看這個,async await 是語法糖,...

C 中mutable關鍵字存在的必要性是什麼?

千月 const宣告是告訴使用者此操作不會改變物件狀態,類似於一種保證。當一些變數不屬於物件狀態時,我們應該也要允許它在const函式中發生改變。舉個簡單的例子,我想統計某個物件介面被呼叫的次數,使用者應該不想const函式被排除吧,mutable不就派上用場了。 YiQiuuu 我的理解是,乙個類...

在C 11中,auto關鍵字的大量使用,會影響編譯速度嗎?

原子筆 沒有測試就別隨便下結論或者杞人憂天吧。編譯器在處理XX a b時,不管XX是auto還是非auto 一樣要去檢視b的型別的,並且需要確認可以拷貝構造。所以理論上來講只會減少編譯時間,包括非自定義型別也類似。 Premature optimization is the root of all ...