1 unsigned char* volatile reg;
這行代碼里reg是指針類型,存儲一個指向unsigned char 類型的指針。volatile修飾的是reg這個變量即為指針
變量是volatile的,但是指針指向的unsigne char 內容不是volatile。在實際使用的時候編譯器對代碼中指針變量reg
本身的操作不會優化,但是對reg所指的內容 "*reg"卻會作為no-volatile內容處理,對“*reg”的操作還是會被優化。
通常這種寫法一般用在對共享的指針變量上,即這個指針變量有可能會被中斷函數修改,將其定義為volatile后編譯器
每次取指針變量值的時候都會從內存載入,這樣即使這個變量已經被別的程序修改 了當前函數用的時候也能得到修改后
的值(否則通常只在函數開始取一次放在寄存器里,以后就一直使用寄存器內的副本)。
1 volatile unsigned char *reg;
這行代碼里volatile修飾的是指針所指的內容,所以這里定義了一個unsigned char類型的指針,並且這個指針指向
的是一個volatile的對象,但是指針變量本身不是volatile。如果對指針變量reg本身進行計算或賦值等操作,是可能被編
編譯器優化的。但是對reg所指的內容*reg的引用卻禁止編譯器優化。因為這個指針所指的是一個volatile的對象,所以
編譯器必須保證對*reg的操作都不被優化。通常在驅動程序的開發中,對硬件寄存器指針的定義,都應該采用這種形式。
1 volatile unsigned char * volatile reg
這樣定義出來的指針本身就是個volatile變量,又指向了volatile的數據內容。
---------------------------------------------【分割線】----------------------------------------------------
volatile 和 const 的合用
從字面上看volatile 和 const 似乎是一個對象的兩個對立屬性,是互斥的。但實際上,兩者是有可能一起修飾同一個
對象的。看看下面的這行聲明。
extern const volatile unsigned int rt_clock;
這個是在RTOS系統內核中常見的一種聲明:rt_clock通常是指系統時鍾,他經常被時鍾中斷進行更新,所以他是volatile
的,易變的。因此在用的時候要讓編譯器每次從內存里面取值。而rt_clock通常只有一個寫者(時鍾中斷),其他地方的使用
通常是只讀的,所以將其聲明為const,表示這里不應該修改這個變量。所以volatile和const是兩個不矛盾的東西,並且一個
對象同時具備這兩種屬性也是具有實際意義的。
注意:上面這個例子里要注意聲明和定義是const的使用,在需要讀寫rt_clock變量的中斷函數里應該如下定義
//在需要讀寫rt_clock變量的中斷函數里如下定義 unsigned char* volatile reg;
//在提供給用戶的聲明頭文件中可以如此申明 external const volatile unsigned int rt_clock;
volatile struct deveregs* const dvp = DEVADDR;
這里的volatile和const實際上是分別修飾了兩個不同的對象:volatile修飾的是指針dvp所指向的類型為struct devregs
的數據結構,這個結構對應設備的硬件寄存器,所以是易變的,不能被優化的;而后面的const修飾的是指針變量dvp。應為硬
件寄存器地址是一個常量,所以這個指針變量定義成const的不能被修改。
---------------------------------------------【分割線】----------------------------------------------------
危險的volatile用法struct deveregs{ unsigned short volatile csr; unsigned short const volatile data; };
我們的原意是希望聲明一個設備的硬件寄存器組,其中有一個16bit的CSR寄存器,這個寄存器可以由程序向設備寫入
控制字,也可以由硬件設備設置反應其工作狀態。另外還有一個16bit的data數據寄存器,這個寄存器只會有硬件進行設置
由程序進行讀入。
看起來這個結構的定義沒有什么問題,也符合實際情況。但是如果執行下面這樣的代碼時,會發生什么情況呢。
struct deveregs* const dvp = DEVADDR;
while((dvp->csr & (READY | ERROR)) == 0){ //NULL wait till done ; }
通過一個non-volatile的結構體指針,去訪問被定義為volatile的結構體成員,編譯器將如何處理?答案是:undefined!
C99標准沒有對編譯器在這種情況下的行為做規定,隨意編譯器有可能正確的將dvp->car作為volatile的變量來處理,使程序
運行正常;也有可能就將dvp->csr作為普通的non-volatile變量來處理,在while當中優化為只有開始的時候取值一次,以后
以后每次循環始終使用第一次取來的值而不再從硬件寄存器里讀取,這樣上面的代碼就有可能死循環。
如果你使用一個volatile的指針來指向一個非volatile的對象,比如將一個non-volatile的結構體賦給一個volatile的指針,
所以對於本例中的代碼,我們應該修改成這樣:
struct devregs{ unsigned short csr; unsigned short data; }; volatile struct devrges* const dvp = DEVADDE;
這樣我們才能保證通過dvp指針去訪問結構體成員的時候都是volatile來處理的。
例:定義為volatile的結構體類型
考察如下代碼:
volatile struct devregs{ ..... }dev1; ..... struct devregs dev2;
作者目的也許是希望定義一個volatile的結構體類型,然后順便定義一個這樣的volatile結構體變量,
因此定義了一個dev2.然而第二次所定義的dev2變量實際上是non-volatile的!因為實際在定義結構
體類型時那個關鍵字volatile修飾的是dev1這個變量而不是struct devregss類型的結構體
所以這個代碼應該寫成這樣:
typedef volatile struct devres{ 。。。 }devregs_t; devregs_t dev1; .. devregs_t dev2;
這樣我們才能得到兩個volatile的結構體變量。
例:多次的間接指針引用
考察如下代碼:
struct bd{ unsigned int state ; unsigned char* data_buff; };
struct devregs{ unsigned int csr; struct bd* tx_bd; struct bd* rx_bd; }; volatile struct devregs* const dvp = DEVADDR; dvp->tx_bd->state = READY; while((dvp->tx_bd->state & EMPTY | ERROR) == 0){ ; }
這樣的代碼常用在對一些DMA設備的發送buffer上。通常這些buffer descriptor(BD)當中的狀態會由硬件進行
struct devregs{ unsigned int csr; volatile struct bd* tx_bd; volatile sturct bd* rx_bd; };
這樣可以保障對state成員的處理是volatile的。不過最為穩妥和清晰的辦法是這樣
volatile struct devregs* const dvp = DEVADDR; volatile struct bd* tx_bd = dvp->tx_bd; tx_bd->state = READY; whlie((tx_bd->state &(EMPTY | ERROR)) == 0) { ; }
這樣在代碼里能絕對保障數據結構的易變性,即使數據結構里面沒有定義好也沒有關系。而且對於日后的維護也有好處
:因為這樣從代碼里一眼就能看出那些數據的訪問是必須保證volatile的。
例:到底哪個volatile可能無效
就在前面的幾個例子,感覺自己可能都已經弄明白了的時候,請最好看這個例子
struct hw_bd{ 。。。。。 volatile unsigned char* volatile buffer; }; struct hw_bd* bdp; ...... bdp->buffer = ...; //1 bdp->buffer = ...; //2
請問上面標記了1和2的代碼哪個是確實在訪問volatile對象,而哪個是undefined的結果。
答案是2是volatile的1是undefined的。來看本例數據結構示意圖
(non-volatile)
bdp->+---------+
| |
| ... ... |
| |
+---------+ (volatile)
| buffer |-->+----------+
+---------+ / /
/ /
/ /
+----------+
/ buffer /
+----------+
/ /
/ /
+----------+
buffer成員本身是通過一個non-volatile的時鍾bdp訪問的,按照C99標准的定義,這就屬於
undefined的情況,因此對bdp->buffer的訪問編譯器不一定能保證是volatile的;雖然buffer
本身可能不是volatile的變量,但是buffer成員是一個指向volatile對象的指針。因此對buffer
成員所指向對象的訪問編譯器可以保證是volatile的,所以bdp->buffer是volatile的。
所以,看似簡單的volatile的關鍵字,用起來還是有很多講究的。
