中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C++中volatile關鍵字如何使用

發布時間:2021-07-06 17:34:11 來源:億速云 閱讀:197 作者:Leah 欄目:編程語言

C++中volatile關鍵字如何使用,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

為什么使用volatile ?

C/C++中的 volatile 關鍵字 和const對應,用來修飾變量,通常用于建立語言級別的memory barrier。這是BS在“The C++ Programming Language”對volatile修飾詞的解釋:A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.

volatile 關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統,硬件或者其他線程等。

遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。聲明時語法:int volatile vInt; 當要求使用 volatile 聲明的變量的值的時候,系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。而且讀取的數據立刻被保存。例如:

volatile int i=10; int a = i; ... // 其他代碼,并未明確告訴編譯器,對 i 進行過操作 int b = i; volatile int i=10;int a = i;...// 其他代碼,并未明確告訴編譯器,對 i 進行過操作int b = i;

volatile 指出 i 是隨時可能發生變化的,每次使用它的時候必須從 i的地址中讀取,因而編譯器生成的匯編代碼會重新從i的地址讀取數據放在 b 中。而優化做法是,由于編譯器發現兩次從 i讀數據的代碼之間的代碼沒有對 i 進行過操作,它會自動把上次讀的數據放在 b 中。而不是重新從 i 里面讀。這樣以來,如果 i是一個寄存器變量或者表示一個端口數據就容易出錯,所以說 volatile 可以保證對特殊地址的穩定訪問。注意,在 VC 6 中,一般調試模式沒有進行代碼優化,所以這個關鍵字的作用看不出來。下面通過插入匯編代碼,測試有無 volatile 關鍵字,對程序最終代碼的影響:

輸入下面的代碼:

#include <stdio.h>  void main() {  int i = 10;  int a = i;   printf("i = %d", a);   // 下面匯編語句的作用就是改變內存中 i 的值  // 但是又不讓編譯器知道  __asm{  mov dword ptr [ebp-4], 20h  }   int b = i;  printf("i = %d", b); } #include <stdio.h> void main(){ int i = 10; int a = i;  printf("i = %d", a);  // 下面匯編語句的作用就是改變內存中 i 的值 // 但是又不讓編譯器知道 __asm{ mov dword ptr [ebp-4], 20h }  int b = i; printf("i = %d", b);}

然后,在 Debug 版本模式運行程序,輸出結果如下:

i = 10i = 32然后,在 Release 版本模式運行程序,輸出結果如下:

i = 10i = 10輸出的結果明顯表明,Release 模式下,編譯器對代碼進行了優化,第二次沒有輸出正確的 i 值。下面,我們把 i 的聲明加上 volatile 關鍵字,看看有什么變化:

#include <stdio.h>  void main() {  volatile int i = 10;  int a = i;  printf("i = %d", a);  __asm {  mov dword ptr [ebp-4], 20h  }   int b = i;  printf("i = %d", b); } #include <stdio.h> void main(){ volatile int i = 10; int a = i;  printf("i = %d", a); __asm { mov dword ptr [ebp-4], 20h }  int b = i; printf("i = %d", b);}

分別在 Debug 和 Release 版本運行程序,輸出都是:

i = 10i = 32

這說明這個 volatile 關鍵字發揮了它的作用。其實不只是“內嵌匯編操縱棧”這種方式屬于編譯無法識別的變量改變,另外更多的可能是多線程并發訪問共享變量時,一個線程改變了變量的值,怎樣讓改變后的值對其它線程 visible。一般說來,volatile用在如下的幾個地方: 1) 中斷服務程序中修改的供其它程序檢測的變量需要加volatile; 2) 多任務環境下各任務間共享的標志應該加volatile; 3) 存儲器映射的硬件寄存器通常也要加volatile說明,因為每次對它的讀寫都可能由不同意義;

2.volatile 指針

和 const 修飾詞類似,const 有常量指針和指針常量的說法,volatile 也有相應的概念:

修飾由指針指向的對象、數據是 const 或 volatile 的:

const char* cpch; volatile char* vpch; const char* cpch;volatile char* vpch;

注意:對于 VC,這個特性實現在 VC 8 之后才是安全的。指針自身的值——一個代表地址的整數變量,是 const 或 volatile 的:

char*const pchc; char*volatile pchv; char*const pchc;char*volatile pchv;

注意:

(1) 可以把一個非volatile int賦給volatile int,但是不能把非volatile對象賦給一個volatile對象。

(2) 除了基本類型外,對用戶定義類型也可以用volatile類型進行修飾。(3) C++中一個有volatile標識符的類只能訪問它接口的子集,一個由類的實現者控制的子集。用戶只能用const_cast來獲得對類型接口的完全訪問。此外,volatile向const一樣會從類傳遞到它的成員。

3. 多線程下的volatile 有些變量是用volatile關鍵字聲明的。當兩個線程都要用到某一個變量且該變量的值會被改變時,應該用volatile聲明,該關鍵字的作用是防止優化編譯器把變量從內存裝入CPU寄存器中。如果變量被裝入寄存器,那么兩個線程有可能一個使用內存中的變量,一個使用寄存器中的變量,這會造成程序的錯誤執行。volatile的意思是讓編譯器每次操作該變量時一定要從內存中真正取出,而不是使用已經存在寄存器中的值,如下:

volatile BOOL bStop = FALSE; // 在一個線程中:  while( !bStop ) { ... } bStop = FALSE; return;  //在另外一個線程中,要終止上面的線程循環: bStop = TRUE; while( bStop ); //等待上面的線程終止, volatile BOOL bStop = FALSE;  // 在一個線程中:  while( !bStop ) { ... } bStop = FALSE; return;  //在另外一個線程中,要終止上面的線程循環: bStop = TRUE; while( bStop ); //等待上面的線程終止,

如果bStop不使用volatile申明,那么這個循環將是一個死循環,因為bStop已經讀取到了寄存器中,寄存器中bStop的值永遠不會變成FALSE,加上volatile,程序在執行時,每次均從內存中讀出bStop的值,就不會死循環了。這個關鍵字是用來設定某個對象的存儲位置在內存中,而不是寄存器中。因為一般的對象編譯器可能會將其的拷貝放在寄存器中用以加快指令的執行速度,例如下段代碼中:

在此段代碼中,nMyCounter的拷貝可能存放到某個寄存器中(循環中,對nMyCounter的測試及操作總是對此寄存器中的值進行),但是另外又有段代碼執行了這樣的操作:nMyCounter -= 1;這個操作中,對nMyCounter的改變是對內存中的nMyCounter進行操作,于是出現了這樣一個現象:nMyCounter的改變不同步。

下面是volatile變量的幾個例子:

1.并行設備的硬件寄存器(如:狀態寄存器

2.一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)

3.多線程應用中被幾個任務共享的變量

看下面例題:

int square(volatile int *ptr){ return *ptr * *ptr;}

這個程序有什么問題嗎? 如果我們不去關心volatile關鍵字的話,那么這個程序你怎么看都會覺得沒多大問題.但是這里

面問題大這ne, 首先參數聲明為volatile就是表明*ptr可能會隨時改變.上述代碼運行時,編譯器可能產生這樣的代碼:

int square(volatile int *ptr){ int a,b; a = *ptr; b = *ptr; return a * b;}

因為你的*ptr是隨時都可以意想不到的變化,所以有可能a*b的時候,a b的值不相同. 這樣你就得到一個錯誤的結果

改正后的程序:

int square(volatile int *ptr){ int a; a = *ptr; return a * a;}

第二個問題,看如下代碼:

#include<iostream> #include<Windows.h> #include<assert.h>  using namespace std;  int main() {  const int a = 2;  int *p = const_cast<int*>(&a);  *p = 3;  cout << a << endl;  system("pause");  return 0; }

我們有理由的認為在內存當中a的值被修改為3,但是結果呢? 我們來看一看

這不科學啊?? 我們再打開監視窗口看一下a的值.

我們都知道監視窗口看到的都是從內存當中拿到的,但是為什么內存當中為3,打印出來就是2呢? 我來解釋一下.

C++編譯器具有優化功能,當你定一個const的常量的時候,系統覺得它不會被改變了,于是做一個優化把該常量存到寄

存器當中,下次訪問的過程更快速一點. 所以當顯示窗口讀取數據的時候,他會直接去寄存器當中讀取數據.而不是去

內存,所以導致我們明明該掉了a的值,卻打印不出來.

這個時候該我們的volatile出馬了,往i前面加一個volatile之后就會解決這個問題,來看結果:

談談C++的volatile關鍵字以及常見的誤解

近期看到C++標準中對volatile關鍵字的定義,發現和java的volatile關鍵字完全不一樣,C++的volatile對并發編程基本沒有幫助。網上也看到很多關于volatile的誤解,于是決定寫這篇文章詳細解釋一下volatile的作用到底是什么。

編譯器對代碼的優化

在講volatile關鍵字之前,先講一下編譯器的優化。

int main() { int i = 0; i++; cout << "hello world" << endl;}

按照代碼,這個程序會在內存中預留int大小的空間,初始化這段內存為0,然后這段內存中的數據加1,最后輸出“hello world”到標準輸出中。但是根據這段代碼編譯出來的程序(加-O2選項),不會預留int大小的內存空間,更不會對內存中的數字加1。他只會輸出“hello world”到標準輸出中。

其實不難理解,這個是編譯器為了優化代碼,修改了程序的邏輯。實際上C++標準是允許寫出來的代碼和實際生成的程序不一致的。雖說優化代碼是件好事情,但是也不能讓編譯器任意修改程序邏輯,不然的話我們沒辦法寫可靠的程序了。所以C++對這種邏輯的改寫是有限制的,這個限制就是在編譯器修改邏輯后,程序對外界的IO依舊是不變的。怎么理解呢?實際上我們可以把我們寫出來的程序看做是一個黑匣子,如果按照相同的順序輸入相同的輸入,他就每次都會以同樣的順序給出同樣的輸出。這里的輸入輸出包括了標準輸入輸出、文件系統、網絡IO、甚至一些system call等等,所有程序外部的事物都包含在內。所以對于程序使用者來說,只要兩個黑匣子的輸入輸出是完全一致的,那么這兩個黑匣子是一致的,所以編譯器可以在這個限制下任意改寫程序的邏輯。這個規則又叫as-if原則。

volatile關鍵字的作用

不知道有沒有注意到,剛剛提到輸入輸出的時候,并沒有提到內存,事實上,程序對自己內存的操作不屬于外部的輸入輸出。這也是為什么在上述例子中,編譯器可以去除對i變量的操作。但是這又會出現一個麻煩,有些時候操作系統會把一些硬件映射到內存上,讓程序通過對內存的操作來操作這個硬件,比如說把磁盤空間映射到內存中。那么對這部分內存的操作實際上就屬于對程序外部的輸入輸出了。對這部分內存的操作是不能隨便修改順序的,更不能忽略。這個時候volatile就可以派上用場了。按照C++標準,對于glvalue的volatile變量進行操作,與其他輸入輸出一樣,順序和內容都是不能改變的。這個結果就像是把對volatile的操作看做程序外部的輸入輸出一樣。(glvalue是值類別的一種,簡單說就是內存上分配有空間的對象,更詳細的請看我的另一篇文章。)

按照C++標準,這是volatile唯一的功能,但是在一些編譯器(如,MSVC)中,volatile還有線程同步的功能,但這就是編譯器自己的拓展了,并不能跨平臺應用。

對volatile常見的誤解

實際上“volatile可以在線程間同步”也是比較常見的誤解。比如以下的例子:

class AObject{public: void wait() { m_flag = false; while (!m_flag) { this_thread::sleep(1000ms); } } void notify() { m_flag = true; }private: volatile bool m_flag;};AObject obj;...// Thread 1...obj.wait();...// Thread 2...obj.notify();...

對volatile有誤解的人,或者對并發編程不了解的人可能會覺得這段邏輯沒什么問題,可能會認為volatile保證了,wait()對m_flag的讀取,notify()對m_flag的寫入,所以Thread 1能夠正常醒來。實際上并不是這么簡單,因為在多核CPU中,每個CPU都有自己的緩存。緩存中存有一部分內存中的數據,CPU要對內存讀取與存儲的時候都會先去操作緩存,而不會直接對內存進行操作。所以多個CPU“看到”的內存中的數據是不一樣的,這個叫做內存可見性問題(memory visibility)。放到例子中就是,Thread 2修改了m_flag對應的內存,但是Thread 1在其他CPU核上運行,所以Thread 1不一定能看到Thread 2對m_flag做的更改。C++11開始,C++標準中有了線程的概念,C++標準規定了什么情況下一個線程一定可以看到另一個線程做的內存的修改。而根據標準,上述例子中的Thread 1可能永遠看不到m_flag變成true,更嚴重的是,Thread 1對m_flag的讀取會導致Undefined Behavior。

從C++標準來說,這段代碼是Undefined Behavior,既然是Undefined Behavior的話,是不是也可能正確執行?是的,熟悉MESI的應該會知道,Thread 2的修改導致緩存變臟,Thread 1讀取內存會試圖獲取最新的數據,所以這段代碼可以正常執行。那是不是就意味著我們可以放心使用volatile來做線程的同步?不是的,只是在這個例子能夠正確執行而已。我們對例子稍作修改,volatile就沒那么好使了。

class AObject{public: void wait() { m_flag = false; while (!m_flag) { this_thread::sleep(1000ms); } } void notify() { m_flag = true; }private: volatile bool m_flag;};AObject obj;bool something = false;... // Thread 1 ... obj.wait(); assert(something)... // Thread 2 ... something = true;obj.notify(); ...

在以上代碼中,Thread 1的assert語句可能會失敗。就如前文所說,C++編譯器在保證as-if原則下可以隨意打亂變量賦值的順序,甚至移除某個變量。所以上述例子中的“something = true"語句可能發生在obj.notify()之后。這樣的話,“assert(something)”就會失敗了。

那么我們可不可能把something也變成volatile?如果something是volatile,我們確實能夠保證編譯出來的程序中的語句順序和源代碼一致,但我們仍然不能保證兩個語句是按照源代碼中的順序執行,因為現代CPU往往都有亂序執行的功能。所謂亂序執行,CPU會在保證代碼正確執行的基礎上,調整指令的順序,加快程序的運算,更多細節我們不在這里展開。我們如果單看Thread 2線程,something和m_flag這兩個變量的讀寫是沒有依賴關系的,而Thread 2線程看不到這兩個變量在其他線程上的依賴關系,所以CPU可能會打亂他們的執行順序,或者同時執行這兩個指令。結果就是,在Thread 1中,obj.wait()返回后,something可能仍然是false,assert失敗。當然,會不會出現這樣的狀況,實際上也和具體的CPU有關系。但是我們知道錯誤的代碼可能會引起錯誤的結果,我們應該避免錯誤的寫法,而這個錯誤就在于誤用了volatile關鍵字,volatile可以避免優化、強制內存讀取的順序,但是volatile并沒有線程同步的語義,C++標準并不能保證它在多線程情況的正確性。

那么用不了volatile,我們該怎么修改上面的例子?C++11開始有一個很好用的庫,那就是atomic類模板,在<atomic>頭文件中,多個線程對atomic對象進行訪問是安全的,并且提供不同種類的線程同步。不同種類的線程同步非常復雜,要涉及到C++的內存模型與并發編程,我就不在此展開。它默認使用的是最強的同步,所以我們就使用默認的就好。以下為修改后的代碼:

class AObject{public: void wait() { m_flag = false; while (!m_flag) { this_thread::sleep(1000ms); } } void notify() { m_flag = true; }private: atomic<bool> m_flag;};

只要把“volatile bool”替換為“atomic<bool>”就可以。<atomic>頭文件也定義了若干常用的別名,例如“atomic<bool>”就可以替換為“atomic_bool”。atomic模板重載了常用的運算符,所以atomic<bool>使用起來和普通的bool變量差別不大。

關于C++中volatile關鍵字如何使用問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

清徐县| 永丰县| 灯塔市| 信宜市| 于田县| 南江县| 绥宁县| 平潭县| 利辛县| 长乐市| 泸水县| 通渭县| 苍山县| 梅河口市| 五莲县| 广河县| 海南省| 大埔县| 清河县| 新兴县| 津南区| 响水县| 东台市| 德兴市| 深圳市| 莱阳市| 松阳县| 文成县| 浮山县| 化德县| 新竹市| 田林县| 临安市| 镇远县| 安溪县| 讷河市| 石渠县| 含山县| 南江县| 二手房| 开平市|