根據c/c++語法,const可以出現的地方,volatile幾乎也都可以出現。但是,const修飾的物件其值不能改變,而volatile修飾的物件其值可以隨意地改變,也就是說,volatile物件值可能會改變,即使沒有任何**去改變它。在這一點上,最典型的例子就是記憶體對映的裝置暫存器和多執行緒中的共享物件。懂得使用volatile也是一門小小的藝術。
使用volatile約束符可以阻止編譯器對**過分優化防止出現一些你意想不到的情況,達不到預期的結果;過頻地使用volatile很可能會增加**尺寸和降低效能。下面舉個例子來說明volatile在優化中的微妙作用。
1.阻止編譯器優化
arm evaluator-7t模擬單機板使用基於記憶體對映的裝置暫存器叫特殊暫存器,用來
控制和互動外圍裝置。cpu對記憶體的操作可以做到按位進行,而特殊暫存器是4位元組對齊並佔四個位元組。你可以象unsigned int變數一樣操作特殊暫存器(有些人可能更喜歡uint32_t,認為這樣體現暫存器占用4個位元組的特點。uint32_t在c99 標頭檔案中有定義)。而這裡,為了體現暫存器本身作為暫存器的含義而非它的物理意義的,我們做如下定義:
typedef uint32_t special_register;
evaluator-7t板子上有乙個按鈕(可以認為是外設之一)。按下該按鈕可以對iopdata暫存器第8位置1,相反,釋放按鈕會將該位重新清0。我們使用列舉方法為iopdata暫存器的第8位置定義乙個掩碼mask:
enum ;
iopdata暫存器對應的位址為0x3ff5008,我們可以用巨集形象地定義iopdata:
#define iopdata (*(special_register *)0x03ff5008)
有了這個定義,我們執行下面的迴圈就可以使cpu一直等待該按鈕被按下:
while ((iopdata & button) == 0);
然而這個期望必須建立在編譯器不對**進行優化的前提假設之上。如果編譯器優化這段**,那麼它會認為在這個迴圈中沒有什麼會改變iopdata而且認為條件判斷結果總是真或假,最終優化的結果是只對(iopdata & button)==0判斷一次,之後的迴圈都不在對其進行判斷,其等同於:
if ((iopdata & button) == 0)
for (;;);
顯然,如果條件判斷結果為真(那麼之後都會認為是真),那麼這段**將會陷入死迴圈。如果判斷為假,那麼迴圈就此結束。可以看出,優化的**效率更高,因為每次迴圈相比原來的執行時間要短。不幸的是,這段優化**使得它根本就不能響應按鈕的每次動作。那麼,如何解決這個問題呢?解決的關鍵就是不要讓編譯器優化這段**,使用volatile就可以辦到這一點。我們修改前面關於iopdata的巨集定義:
#define iopdata (*(special_register volatile *)0x03ff5008)
這個定義將iopdata 定義為volatile型別的暫存器。volatile隱含地告訴編譯器特殊暫存器可能會改變內容,即使沒有任何顯式地**去改變它的內容。這樣一來,編譯器就不對iopdata作優化,而是每次都去訪問iopdata,這其實正是我們所期望的。
2.無意中降低了效率
有時候,如果不注意的話,使用volatile會無意中降低**效率。舉個例子。evaluator-7t有乙個七段數碼顯示器見下圖:
在iopdata 暫存器中第10到16位用來控制顯示器的每一段。比如第10位就是用來控制頂部的那段顯示,置1則點亮它,清0則熄滅它。我們可以定義乙個掩碼mask來覆蓋從第10到16的所有位:
enum ;
假設變數b用來控制這7段顯示器的每一段顯示,並且b的值已經你想要設定值(準備用來顯示哪幾段和熄滅哪幾段,其它無關的位均為0)。那麼你想要改變設定新的顯示方式的操作就是:
iopdata = b;
但是這種賦值可能會改變第10到16位之外的其它位,這是我們不期望的。所以,採用下面的方法更好:
iopdata |= b
但是,使用 |= 並不能熄滅那些已經點亮的顯示段(1 | 0 -> 1),所以我們可以用下面的函式達到目的:
void display_put(uint32_t b)
不過,可能沒想到的是這樣的操作在無意中降低了**效率。因為我們定義iopdata為
volatile 型別,它阻止了編譯器對**的優化,要求任何讀寫iopdata的操作都死死板板地進行。iopdata &= ~display的等價表現為iopdata = iopdata & ~display,也就是先從iopdata讀出內容然後與上~display,最後又回寫iopdata。同理,iopdata |=b也有相似的過程。整個過程分別有2次讀iopdata和2次寫iopdata的操作。如果iopdata不使用volatile,那麼編譯器會要求將iopdata & ~display的結果放在cpu暫存器中,直到完成iopdata |= b操作才寫回特殊暫存器iopdata。顯然後者較之前者分別省掉了1次讀iopdata和1次i寫opdata的耗時操作(外設操作是最耗時的),效率要高很多。如果你想使用volatile但又能使能優化功能,你可以將函式作如下的修改:
void display_put(uint32_t b)
這樣做有點煩瑣,下面的等效方法更簡單:
void display_put(uint32_t b)
結論:從該例子看出,它並不鼓勵使用volatile,即使要用也要很小心,因為volatile可能在無意中降低了**效率,而你卻無法察覺。但是,我們說,不鼓勵並不是說就不能或不要用,而是要懂得何時用,怎麼用好它。其所謂智用了。
你今天volatile了嗎? 兌現允諾
對任意資料型別t,c提供一種標準內建的轉換。這個轉化可以完成從t指標到volatile t指標的轉換,並規定其逆過程即volatile t指標向t指標轉換為非法。const指標和volatile指標在轉換規則方面具有相似性。本篇文章就後乙個觀點繼續深入 本人認為const指標的轉換規則與const指...
今天你笑了嗎?
1 有次等公共汽車時,開過去一輛寶馬,旁邊一位高人對他身邊的人說 看,剛過去那輛就是ibm.2 我一朋友在聯通實習,一天,一老頭走近來,劈頭蓋臉就來句 給我辦張移動卡,好吧?然後我那朋友頭也不抬的就來句 師傅,有人來砸場子 3 同事去見客戶,可能是緊張,一開口便是 劉先生你好,請問你貴姓啊?汗啊 4...
今天,你學習了嗎?
1 對meta的理解 在每個html的頁面中,有這樣一行 charset utf 8 因為每次新建html檔案自動生成這行,一直沒在乎過這個標籤有什麼意義。今天看到這樣的 name viewport content width device width,minimum scale 1.0,maxim...