1. 程式人生 > >C++關鍵字mutable和volatile

C++關鍵字mutable和volatile

mutable和volatile

       很少遇到這兩個關鍵字,學嵌入式估計知道後者,深入研究 C++ 的估計知道前者。

     (1)mutable

       在 C++ 中,mutable 是為了突破 const 的限制而設定的。被 mutable 修飾的變數,將永遠處於可變的狀態,即使在一個 const 函式中,甚至結構體變數或者類物件為 const,其 mutable 成員也可以被修改。

[cpp] view plain copy

 print?


  1. struct  ST  
  2. {  
  3.   int a;  
  4.   mutableint b;  
  5. };  
  6. const ST st={1,2};  
  7. st.a=11;//編譯錯誤
  8. st.b=22;//允許

       mutable 在類中只能夠修飾非靜態資料成員。mutable 資料成員的使用看上去像是騙術,因為它能夠使 const 函式修改物件的資料成員。然而,明智地使用 mutable 關鍵字可以提高程式碼質量,因為它能夠讓你向用戶隱藏實現細節,而無須使用不確定的東西。我們知道,如果類的成員函式不會改變物件的狀態,那麼這個成員函式一般會宣告成 const 的。但是,有些時候,我們需要在 const 的函式裡面修改一些跟類狀態無關的資料成員,那麼這個資料成員就應該被 mutalbe 來修飾。

[cpp] view plain copy

 print?


  1. class  ST  
  2. {  
  3. int a;  
  4. mutableint showCount;  
  5. void Show()const;  
  6. …  
  7. };  
  8. ST::Show()  
  9. {  
  10. //顯示程式碼
  11. a=1;//錯誤,不能在const成員函式中修改普通變數
  12. showCount++;//正確
  13. }  

       const 承諾的是一旦某個變數被其修飾,那麼只要不使用強制轉換 (const_cast),在任何情況下該變數的值都不會被改變,無論有意還是無意,而被 const 修飾的函式也一樣,一旦某個函式被 const 修飾,那麼它便不能直接或間接改變任何函式體以外的變數的值,即使是呼叫一個可能造成這種改變的函式都不行。這種承諾在語法上也作出嚴格的保證,任何可能違反這種承諾的行為都會被編譯器檢查出來。

       mutable 的承諾是如果某個變數被其修飾,那麼這個變數將永遠處於可變的狀態,即使在一個 const 函式中。這與 const 形成了一個對稱的定義,一個永遠不變,而另外一個是永遠可變。

       看一個變數或函式是否應該是 const,只需看它是否應該是 constant 或 invariant,而看一個變數是否應該是 mutable,也只需看它是否是 forever mutative。

       這裡出現了令人糾結的 3 個問題:

       1、為什麼要保護類的成員變數不被修改?

       2、為什麼用 const 保護了成員變數,還要再定義一個 mutable 關鍵字來突破 const 的封鎖線?

       3、到底有沒有必要使用 const 和 mutable 這兩個關鍵字?

       保護類的成員變數不在成員函式中被修改,是為了保證模型的邏輯正確,通過用 const 關鍵字來避免在函式中錯誤的修改了類物件的狀態。並且在所有使用該成員函式的地方都可以更準確的預測到使用該成員函式的帶來的影響。而 mutable 則是為了能突破 const 的封鎖線,讓類的一些次要的或者是輔助性的成員變數隨時可以被更改。沒有使用 const 和 mutable 關鍵字當然沒有錯,const 和 mutable 關鍵字只是給了建模工具更多的設計約束和設計靈活性,而且程式設計師也可以把更多的邏輯檢查問題交給編譯器和建模工具去做,從而減輕程式設計師的負擔。

     (2)volatile

       像 const 一樣,volatile 是一個型別修飾符。volatile 修飾的資料,編譯器不可對其進行執行期寄存於暫存器的優化。這種特性,是為了滿足多執行緒同步、中斷、硬體程式設計等特殊需要。遇到這個關鍵字宣告的變數,編譯器對訪問該變數的程式碼就不再進行優化,從而可以提供對特殊地址的直接訪問。

       volatile 原意是“易變的”,但這種解釋簡直有點誤導人,應該解釋為 “直接存取原始記憶體地址” 比較合適。“易變” 是相對與普通變數而言其值存在編譯器(優化功能)未知的改變情況(即不是通過執行程式碼賦值改變其值的情況),而是因外在因素引起的,如多執行緒,中斷等。編譯器進行優化時,它有時會取一些值的時候,直接從暫存器裡進行存取,而不是從記憶體中獲取,這種優化在單執行緒的程式中沒有問題,但到了多執行緒程式中,由於多個執行緒是併發執行的,就有可能一個執行緒把某個公共的變數已經改變了,這時其餘執行緒中暫存器的值已經過時,但這個執行緒本身還不知道,以為沒有改變,仍從暫存器裡獲取,就導致程式執行會出現未定義的行為。並不是因為用 volatile 修飾了的變數就是“易變”了,假如沒有外因,即使用 volatile 定義,它也不會變化。而加了 volatile 修飾的變數,編譯器將不對其相關程式碼執行優化,而是生成對應程式碼直接存取原始記憶體地址。

       一般說來,volatile 用在如下的幾個地方:

       1、中斷服務程式中修改的供其它程式檢測的變數需要加 volatile;

       2、多工環境下各任務間共享的標誌應該加 volatile;

       3、儲存器對映的硬體暫存器通常也要加 volatile 說明,因為每次對它的讀寫都可能有不同意義;

       使用該關鍵字的例子如下:

[cpp] view plain copy

 print?


  1. volatileint i=10;  
  2. int a = i;  
  3. …  
  4. //其他程式碼,並未明確告訴編譯器,對i進行過操作
  5. int b = i;  

        volatile 指出 i 是隨時可能發生變化的,每次使用它的時候必須從i的地址中讀取,因而編譯器生成的彙編程式碼會重新從 i 的地址讀取資料放在 b 中。而優化做法是,由於編譯器發現兩次從 i 讀資料的程式碼之間的程式碼沒有對 i 進行過操作,它會自動把上次讀的資料(即10)放在 b 中,而不是重新從 i 裡面讀。這樣以來,如果 i 是一個暫存器變數或者表示一個埠資料就容易出錯,所以說 volatile 可以保證對特殊地址的直接訪問。

[html] view plain copy

 print?


  1. //addr為volatile變數  
  2. addr=0x57;   
  3. addr=0x58;  

       如果上述兩條語句是對外部硬體執行不同的操作,那麼編譯器就不能像對待普通的程式那樣對上述語句進行優化只認為 “addr=0x58;” 而忽略第一條語句(即只產生一條機器程式碼),此時編譯器會逐一的進行編譯併產生相應的機器程式碼(兩條)。

       volatile 總是與優化有關,編譯器有一種技術叫做資料流分析,分析程式中的變數在哪裡賦值、在哪裡使用、在哪裡失效,分析結果可以用於常量合併,常量傳播等優化,進一步可以死程式碼消除。但有時這些優化不是程式所需要的,這時可以用 volatile 關鍵字禁止做這些優化,它有下面的作用:
  1、不會在兩個操作之間把 volatile 變數快取在暫存器中。在多工、中斷等環境下,變數可能被其他的程式改變,編譯器自己無法知道,volatile 就是告訴編譯器這種情況。
  2、不做常量合併、常量傳播等優化,所以像下面的程式碼,if的條件不會當作無條件真。 

[cpp] view plain copy

 print?


  1. volatileint i = 1;   
  2.   if (i > 0)  
  3.        …   

       3、對 volatile 變數的讀寫不會被優化掉。如果你對一個變數賦值但後面沒用到,編譯器常常可以省略那個賦值操作,然而對 Memory Mapped IO 的處理是不能這樣優化的。

【原文連結】