c++ - address - volatile embedded c



什麼樣的優化在C++中“易變”? (6)

我正在查找關鍵字volatile和它的用途,我得到的答案非常多:

它用於防止編譯器優化代碼。

有一些例子,例如輪詢內存映射硬件:沒有volatile ,輪詢循環將被刪除,因為編譯器可能會認識到條件值永遠不會改變。 但由於只有一個例子或兩個例子,它讓我思考:在避免不必要的優化方面,我們是否還需要使用volatile ? 條件變量是唯一需要volatile的地方嗎?

我認為優化是特定於編譯器的,因此未在C ++規範中指定。 這是否意味著我們必須通過直覺,說嗯,我懷疑我的編譯器將廢除這個,如果我不宣布該變量為volatile或有任何明確的規則可以通過?

https://src-bin.com


Answer #1

C ++程序的可觀察行為取決於對volatile變量的讀寫,以及對輸入/輸出函數的任何調用。

這需要的是,對volatile變量的所有讀寫都必須按照它們在代碼中出現的順序發生,並且它們必鬚髮生。 (如果編譯器違反了其中一條規則,則會破壞as-if規則。)

就這樣。 當您需要指示讀取或寫入變量被視為可觀察的效果時,可以使用它。 (注意, “C ++和雙重鎖定的危險”文章對此有所了解。)

因此,為了回答標題問題,它會阻止任何可能重新排序相對於其他volatile變量的volatile變量評估的優化。

這意味著更改的編譯器:

int x = 2;
volatile int y = 5;
x = 5;
y = 7;

int x = 5;
volatile int y = 5;
y = 7;

很好,因為x的值不是可觀察行為的一部分(它不是易變的)。 什麼是不錯的是將賦值從5更改為賦值為7,因為寫5是一個可觀察的效果。


Answer #2

Volatile不會嘗試將數據保存到cpu寄存器(比內存快100倍)。 它必須在每次使用時從內存中讀取它。


Answer #3

條件變量不是需要volatile的地方; 嚴格來說,它只在設備驅動程序中需要。

volatile保證對對象的讀取和寫入不會被優化掉,或者相對於另一個volatile重新排序。 如果您正在忙於循環另一個線程修改的變量,則應將其聲明為volatile 。 但是,你不應該忙著循環。 由於該語言並非真正為多線程設計,因此不太受支持。 例如,編譯器可能會在循環之後到循環之前將寫入移動到非易失性變量,從而違反鎖定。 (對於無限旋轉,這可能只發生在C ++ 0x下。)

當您調用線程庫函數時,它充當內存柵欄,編譯器將假定所有值都已更改 - 基本上所有內容都是volatile。 這可以由任何線程庫指定或默認實現,以保持車輪平穩轉動。

C ++ 0x可能沒有這個缺點,因為它引入了正式的多線程語義。 我並不熟悉這些更改,但為了向後兼容,它不需要聲明之前沒有的任何volatile。


Answer #4

考慮volatile變量的一種方法是想像它是一個虛擬屬性; 寫入甚至讀取可能會做編譯器無法知道的事情。 用於寫入/讀取volatile變量的實際生成代碼只是內存寫入或讀取(*),但編譯器必須將代碼視為不透明; 它不能做出任何可能多餘的假設。 問題不僅在於確保編譯的代碼注意到某些內容導致變量發生變化。 在某些系統上,即使是內存讀取也可以“做”事情。

(*)在某些編譯器中,可以將volatile變量作為不同的操作添加,減去,遞增,遞減等。 編譯器編譯可能很有用:

  volatilevar++;

  inc [_volatilevar]

因為後一種形式在許多微處理器上可能是原子的(儘管不是在現代多核PC上)。 但是,重要的是要注意,如果聲明是:

  volatilevar2 = (volatilevar1++);

正確的代碼不會是:

  mov ax,[_volatilevar1] ; Reads it once
  inc [_volatilevar]     ; Reads it again (oops)
  mov [_volatilevar2],ax

也不

  mov ax,[_volatilevar1]
  mov [_volatilevar2],ax ; Writes in wrong sequence
  inc ax
  mov [_volatilevar1],ax

反而

  mov ax,[_volatilevar1]
  mov bx,ax
  inc ax
  mov [_volatilevar1],ax
  mov [_volatilevar2],bx

以不同方式編寫源代碼將允許生成更有效(並且可能更安全)的代碼。 如果'volatilevar1'不介意被讀兩次並且'volatilevar2'不介意在volatilevar1之前寫入,那麼將語句分成

  volatilevar2 = volatilevar1;
  volatilevar1++;

將允許更快,更安全的代碼。


Answer #5

通常編譯器假定程序是單線程的,因此它完全了解變量值發生了什麼。 然後,智能編譯器可以證明程序可以轉換為具有等效語義但性能更好的另一個程序。 例如

x = y+y+y+y+y;

可以轉化為

x = y*5;

但是,如果變量可以在線程外部進行更改,則編譯器無法通過簡單地檢查這段代碼來完全了解正在發生的事情。 它不能再像上面那樣進行優化。 ( 編輯:它可能在這種情況下;我們需要更複雜的例子

默認情況下,對於性能優化,假定單線程訪問。 這種假設通常是正確的。 除非程序員明確地使用volatile關鍵字指示。


Answer #6

除非您使用的是嵌入式系統,或者您正在編寫硬件驅動程序,其中內存映射用作通信方式,否則您永遠不應該使用volatile

考慮:

int main()
{
    volatile int SomeHardwareMemory; //This is a platform specific INT location. 
    for(int idx=0; idx < 56; ++idx)
    {
        printf("%d", SomeHardwareMemory);
    }
}

必須生成如下代碼:

loadIntoRegister3 56
loadIntoRegister2 "%d"
loopTop:
loadIntoRegister1 <<SOMEHARDWAREMEMORY>
pushRegister2
pushRegister1
call printf
decrementRegister3
ifRegister3LessThan 56 goto loopTop

而沒有volatile可能是:

loadIntoRegister3 56
loadIntoRegister2 "%d"
loadIntoRegister1 <<SOMEHARDWAREMEMORY>
loopTop:
pushRegister2
pushRegister1
call printf
decrementRegister3
ifRegister3LessThan 56 goto loopTop

關於volatile的假設是可以改變變量的存儲位置。 每次使用變量時,您都強制編譯器從內存加載實際值; 並告訴編譯器不允許在寄存器中重用該值。





volatile