程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> c++關鍵字之:volatile

c++關鍵字之:volatile

編輯:關於C++

volatile 是“易變的”、“不穩定”的意思。volatile是 c++ 的一個關鍵字,用來解決在“共享”環境下容易出現的讀取錯誤的問題。

在單任務的環境中,一個函數體內部,如果在兩次讀取變量的值之間的語句沒有對變量的值進行修改,那麼編譯器就會設法對可執行代碼進行優化。由於訪問寄存器的速度要快過RAM(從RAM中讀取變量的值到寄存器),以後只要變量的值沒有改變,就一直從寄存器中讀取變量的值,而不對RAM進行訪問。

這雖然在單任務環境下是一個優化過程,但是卻是多任務環境下問題的起因。

多任務環境中,雖然在一個函數體內部,在兩次讀取變量之間沒有對變量的值進行修改,但是該變量仍然有可能被其他的程序(如中斷程序、另外的線程等)所修改。如果還是從寄存器而不是從RAM中讀取變量的值,就會出現被修改了的比阿郎的之不能及時的反應的問題。如下程序對這一現象進行了模擬:

#include 
using namespace std;

int main(int argc,char* argv[])
{
    int i=10;
    int a=i;
    cout<

程序在VS2012環境下生成 release 版本(一定要極端優化,vs編譯環境下選擇優化 速度最大化 /O2),輸出結果也是:
10
10

順便說一下,ebp是擴展基址指針寄存器(extended base pointer) 其內存放一個指針,該指針指向系統棧最上面一個棧幀的底部。

本來事實上已經通過內聯匯編,修改過的值,為什麼打印出來還是10呢

但是如果:

將 int i=10; 前加 volatile 就不會發生這種情況了。

跟蹤匯編代碼可以發現,凡是聲明為 volatile 的變量,每次拿到的值都是從內存中直接讀取的。

以下實驗在 vs2012 release 環境下進行。

不加 volatile

    int i=10;
    int a=i;
    tmp(a);
00D71273  push        dword ptr ds:[0D73024h]  
00D71279  mov         ecx,dword ptr ds:[0D7303Ch]  
00D7127F  push        0Ah  
00D71281  call        dword ptr ds:[0D7302Ch]  
00D71287  mov         ecx,eax  
00D71289  call        dword ptr ds:[0D73028h]  

    _asm{
        mov dword ptr [ebp-4],80
00D7128F  mov         dword ptr [ebp-4],50h  
    }

    int b=i;
    tmp(b);
00D71296  push        dword ptr ds:[0D73024h]  
00D7129C  mov         ecx,dword ptr ds:[0D7303Ch]  
00D712A2  push        0Ah  
00D712A4  call        dword ptr ds:[0D7302Ch]  
00D712AA  mov         ecx,eax  
00D712AC  call        dword ptr ds:[0D73028h]

加了 volatile

    tmp(a);
01201274  push        dword ptr ds:[1203024h]  
    volatile int i=10;
0120127A  mov         dword ptr [i],0Ah  
    int a=i;
01201281  mov         eax,dword ptr [i]  
    tmp(a);
01201284  mov         ecx,dword ptr ds:[120303Ch]  
0120128A  push        eax  
0120128B  call        dword ptr ds:[120302Ch]  
01201291  mov         ecx,eax  
01201293  call        dword ptr ds:[1203028h]  

    _asm{
        mov dword ptr [ebp-4],80
01201299  mov         dword ptr [i],50h  
    }

    int b=i;
012012A0  mov         eax,dword ptr [i]  
    tmp(b);
012012A3  push        dword ptr ds:[1203024h]  
012012A9  mov         ecx,dword ptr ds:[120303Ch]  
012012AF  push        eax  
    tmp(b);
012012B0  call        dword ptr ds:[120302Ch]  
012012B6  mov         ecx,eax  
012012B8  call        dword ptr ds:[1203028h]

由於編譯器的極端優化,可以很明顯的看到,在沒有加 volatile 的情況下,甚至編譯器是直接使用操作數 0Ah 進行運算的。

而在加了 volatile 的情況下,每次都是從 ptr [i] 中讀取。

而且在速度極端優化的情況下,

void tmp(int t) {
    cout<

也自動 inline 處理了。

但是這裡也拋出一個問題,為什麼是 [ebp-4] 修改的就是i的值,更奇怪的是,我如果如下這樣寫代碼,那改的會是哪個變量的值呢:

#include 
using namespace std;

void tmp(int t) {
    cout<

為什麼分配的總是 [ebp-4] 是復制給 a 的值呢?試驗過,如果將 ic 賦值給 a,那 [ebp-4] 存放的值將會是 ic

這裡寫圖片描述

閱讀以上程序,注意以下幾個要點:
(1)以上代碼必須在release模式下考查,因為只有Release模式(嚴格說需要在速度最大優化 /O2)下才會對程序代碼進行優化,而這種優化在變量共享的環境下容易引發問題。

(2)凡是需要被多個任務共享的變量(如可能被中斷服務程序訪問的變量、被其他線程訪問的變量等),都應聲明為 volatile 變量。而且為了提高執行效率,要減少對 volatile 不必要的使用。

(3)由於優化可能會將一些“無用”的代碼徹底去除,所以,如果確實希望在可執行文件中保留這部分代碼,也可以將其中的變量聲明為 volatile:

int main(int argc,char* argv[])
{
    int s,i,j;
    for(i=0;i<100;++i)
        for(j=0;j<100;++j)
            s=5;

    return 0;
}

在生成 release 版本的程序時,由於循環體每次給 s 的值不變(簡化為執行1次),或者說沒有使用(1次都沒有),但如果此時程序猿是希望循環拖延時間,寫成 volatile 就可以了。

附錄:問題

1)一個參數既可以是const還可以是volatile嗎?解釋為什麼

是的。一個例子是只讀的狀態寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應該試圖去修改它。

2) 一個指針可以是volatile 嗎?解釋為什麼

是的。盡管這並不很常見。一個例子是當一個中斷服務子程序修該一個指向一個buffer的指針時。

3) 下面的函數有什麼錯誤:

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

這段代碼的目的是用來返指針*ptr指向值的平方,但是,由於*ptr指向一個volatile型參數,編譯器將產生類似下面的代碼:

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

由於*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結果,這段代碼可能返不是
你所期望的平方值!正確的代碼如下:

long square(volatile int *ptr) { 
    int a; 
    a = *ptr; 
    return a * a; 
} 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved