[轉][蒐集]C語言中volatile關鍵字

c語言中volatile關鍵字
volatile關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改。
用volatile關鍵字聲明的變量i每一次被訪問時,執行部件都會從i相應的內存單元中取出i的值。
沒有用volatile關鍵字聲明的變量i在被訪問的時候可能直接從cpu的寄存器中取值(因為之前i被訪問過,也就是說之前就從內存中取出i的值保存到某個寄存器中),之所以直接從寄存器中取值,而不去內存中取值,是因為編譯器優化代碼的結果(訪問cpu寄存器比訪問ram快的多)。
以上兩種情況的區別在於被編譯成彙編代碼之後,兩者是不一樣的。之所以這樣做是因為變量i可能會經常變化,保證對特殊地址的穩定訪問。
========================================
volatile關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改
,比如:操作系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的
代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。
使用該關鍵字的例子如下:
int volatile nVint;
  當要求使用volatile 聲明的變量的值的時候,系統總是重新從它所在的內存讀取數據,即
使它前面的指令剛剛從該處讀取過數據。而且讀取的數據立刻被保存。
例如:
volatile int i=10;
int a = i;

//其他代碼,並未明確告訴編譯器,對i進行過操作
int b = i;
  volatile 指出 i是隨時可能發生變化的,每次使用它的時候必須從i的地址中讀取,因而編
譯器生成的彙編代碼會重新從i的地址讀取數據放在b中。而優化做法是,由於編譯器發現兩次從
i讀數據的代碼之間的代碼沒有對i進行過操作,它會自動把上次讀的數據放在b中。而不是重新
從i裏面讀。這樣以來,如果i是一個寄存器變量或者表示一個端口數據就容易出錯,所以說vola
tile可以保證對特殊地址的穩定訪問。
  注意,在vc6中,一般調試模式沒有進行代碼優化,所以這個關鍵字的作用看不出來。下麵
通過插入彙編代碼,測試有無volatile關鍵字,對程序最終代碼的影響:
  首先,用classwizard建一個win32 console工程,插入一個voltest.cpp文件,輸入下面的
代碼:
 
#include <stdio.h>
void main()
{
 int i=10;
 int a = i;

 printf(“i= %d/n”,a);
 //下面彙編語句的作用就是改變內存中i的值,但是又不讓編譯器知道
 __asm {
  mov dword ptr [ebp-4], 20h
 }

 int b = i;
 printf(“i= %d/n”,b);
}
然後,在調試版本模式運行程序,輸出結果如下:
i = 10
i = 32
然後,在release版本模式運行程序,輸出結果如下:
i = 10
i = 10
輸出的結果明顯表明,release模式下,編譯器對代碼進行了優化,第二次沒有輸出正確的i值。

下面,我們把 i的聲明加上volatile關鍵字,看看有什麼變化:
#include <stdio.h>
void main()
{
 volatile int i=10;
 int a = i;
 printf(“i= %d/n”,a);
 __asm {
  mov dword ptr [ebp-4], 20h
 }
 int b = i;
 printf(“i= %d/n”,b);
}    
分別在調試版本和release版本運行程序,輸出都是:
i = 10
i = 32
這說明這個關鍵字發揮了它的作用!
===============================================================
volatile宣告 在C裡面有volatile這個宣告,通常是說這個變數會被外在routine改變, 在kernel裡面通常是指會被interrupt handler(有時就是硬體中斷的routine) 改變值,也就是被非同步的改變的變數。例如 unsigned long vloatile jiffies; jiffies在kernel是時間每次hardware的中斷會來改這個值 在asm裡面是說這個東西compiler時,gcc不要雞婆作optimized,因為 最佳化的結果,compiler會把code按照他想的方法放到記憶體裡, 但是有的code我們需要特定指定他一定要在某個記憶體上, 在kernel裡常有這樣情形發生,我們可以用 __asm__ __volatile__宣告一段assembly的code是不要做最佳化的。 例如cli sti #define disable() __asm__ __volatile__ (“cli”); #define enable() __asm__ __volatile__ (“sti”);

[ 例1 ]

Volatile這玩意兒用在單晶片的C語言較多

例如:
Volatile char wait;
void xxx(void)
{
wait=1;
while (wait!=0);
…..
…..
}
void timer0(void) interrupt 1
{
wait=0;
…….
}

xxx()中就是等一個timer中斷才執底下工作.
如果不寫Volatile會被編譯器省略掉.
因為wait=1;何來wait會等於0

[ 例2 ]

volatile sig_atomic_t read_flag = 1;

volatile是一個關鍵字(keyword),用來修飾詞資料型態,與const有對應的關係。
是語言關鍵字的話,就不會出現在標頭檔內的定義。
volatile sig_atomic_t read_flag = 1;
read_flag是一個為sig_atomic_t的資料型態,透過volatile的修飾,說明sig_atomic_t在程式中,可以在任何的時間下被修改 ,
也就是說,你的程式可能同一時間不止一個程序在使用。
可能主程式main在使用的同時,I/O Device或另一個thread也在利用或指定這個read_flag
來透過溝通的作用。

[ 例3 ]


[ 參考 ]


=====================================================================

C/C++ 的volatile
C/C++中的volatile使用時機?
.不知各位對volatile(揮發性的)這個字陌不陌生? 我相信大家在一些程式或多或少都看
 過這個字眼, 但是究竟要在何種場合用它呢?
.當然一定是有需要, C/C++才會有這個保留字, 否則只是增加programmer的困擾而已
.有2兩個場合(I/O & multithread program), 供各位參考!
.請大家check自己的程式中(尤其是第2個場合), 若有的話請記得加上volatile
1. I/O, 假設有一程式片斷如下
       U8   *pPort;
       U8   i, j, k;
  
       pPort = (U8 *)0x800000;

       i = *pPort; 
       j = *pPort; 
       k = *pPort;     

    以上的i, j, k很有可能被compiler最佳化而導致產生
       i = j = k = *pPort;
    的code, 也就是說只從pPort讀取一次, 而產生 i = j = k 的結果, 但是原本的程式的目
    的是要從同一個I/O port讀取3次的值給不同的變數, i, j, k的值很可能不同(例如從此
    I/O port 讀取溫度), 因此i = j = k的結果不是我們所要的
    怎麼辦 => 用volatile, 將
       U8   *pPort;
    改為
       volatile U8   *pPort;
    告訴compiler, pPort變數具有揮發性的特性, 所以與它有關的程式碼請不要作最佳化動作. 因而
       i = *pPort; 
       j = *pPort; 
       k = *pPort; 
    此三列程式所產生的code, 會真正地從pPort讀取三次, 從而產生正確的結果
2. Global variables in Multithread program
    => 這是在撰寫multithread program時最容易被忽略的一部份
    => 此原因所造成的bug通常相當難解決(因為不穩定)
    假設有以下程式片斷, thread 1 & thread 2共用一個global var: gData
        thread 1:                                thread 2:                           
                                                                                     
            …                                      ….                            
            int  gData;                              extern int gData;               
                                                                                     
            while (1)                                int  i, j, k;                   
            {                                                                        
                ….                                 for (i = 0; i < 1000; i++)
                gData = rand();                      {                               
                …..                                    /* A */
            }                                            j = gData;                  
                                                         ….                        
            ….                                     }                                   
    在thread 2的for loop中, 聰明的compiler看到gData的值, 每次都重新從memory load到register,
    實在沒效率, 因此會產生如下的code(注意,tmp也可以更進一步的用register取代):
       tmp = gData;
       for (i = 0; i < 1000; i++       
       {                               
           /* A */
           j = tmp;                  
           ….                        
       }                               
    也就是gData只讀取一次, 這下子問題來了, 說明如下:
    .thread 2在執行for loop到j = gData的前一列(A)的時候(假設此時gData=tmp=5), 被切換到thread 1執行
    .在thread 1的while loop中透過gData = rand(), 對gData做了修改(假設改為1), 再切換回thread 2執行
    .繼續執行 j = gData, 產生j = 5的結果
    .但是正確的結果應該是 j = 1
    怎麼辦 => 也是用volatile,
    在thread 1中, 將
        int  gData;
    改為
        volatile int  gData;

    在thread 2中, 將
        extern int  gData;
    改為
        extern volatile int  gData;  

=======================================================================================
volatile 為一關鍵字 加在變數的前面
被 volatile 宣告的變數 將不會使用最佳化編譯
有時一個變數的值改變了 compiler 並不會馬上將他寫入記憶體中
而會先把結果放在CPU暫存器中 等到處理結束之後 才寫入記憶體
若說這個變數是多執行緒的flag 其他的執行緒要透過這個變數來反應
而這個值卻又沒有寫入記憶體 這時便會發生意想不到的結果
又或者是這變數為一個硬體的暫存器 會被硬體所改變
然而compiler 並沒有正確的將值從硬體暫存器取出來 
而是將自己暫存的值拿來使用
這種情況 就是要用volatile 來宣告變數 告訴compiler不要自己暫存變數來提升速度
如此這個變數有任何的改變 便會馬上反應出來
===============================================================================
volatile變數代表其所儲存的內容會不定時地被改變,宣告volatile變數用來告訴編譯器 (Compiler) 不要對該變數做任何最佳化操作,凡牽涉讀取該volatile變數的操作,保證會到該變數的實體位址讀取,而不會讀取CPU暫存器的內容 (提升效能) 。舉個例子,某一硬體狀態暫存器 (Status Register)就必須宣告volatile關鍵字,因為該狀態暫存器會隨時改變,宣告volatile便可確保每次讀取的內容都是最新的。 


Volatile關鍵字的應用範疇 

1. 硬體暫存器,如狀態暫存器。
2. 多執行緒所共用的全域變數。
3. 中斷服務函式 (Interrupt Service Rountie,ISR)所使用的全域變數。





Volatile陷阱 
想想底下範例是否有問題? 


#include <stdio.h>



int square(volatile int *var)

{

    return *var **var;

}



int main(void)

{

    int    var = 5;



    printf("result: %dn", square(&var));

    return 0;

}





其問題在於square函式的平方算式,*var**var,此指令代表到var位址讀取其內容。然而,var位址可能儲存硬體暫存器,這些暫存器內容會隨時間而改變 (例如: 狀態暫存器),有可能第一次讀取的時候為4, 下一次讀取為5, 導致計算出來的值不正確。 


因此,避免此錯誤發生便是在square函式宣告一local變數,看底下程式範例較為清楚: 


#include <stdio.h>



int square(volatile int *var)

{

    int    local_var = *var;



    return local_var * local_var;

}



int main(void)

{

    int    var = 5;



    printf("result: %dn", square(&var));

    return 0;

}

未經允許不得轉載:GoMCU » [轉][蒐集]C語言中volatile關鍵字