共用方式為


警告 C26837

比較子 compfunc 式的值已從目的地位置 dest 載入到非揮發性讀取。

此規則已在 Visual Studio 2022 17.8 中新增。

備註

InterlockedCompareExchange 式及其衍生專案,例如 InterlockedCompareExchangePointer,會在指定的值上執行不可部分完成的比較和交換作業。 Destination如果值等於Comparand值,交換值會儲存在 所Destination指定的位址中。 否則,不會執行任何作業。 函式 interlocked 提供簡單的機制,可用來同步存取多個線程共用的變數。 此函式相對於呼叫其他 interlocked 函式而言是不可部分完成的。 誤用這些函式可能會產生與預期不同的物件程式代碼,因為優化可能會以非預期的方式變更程式代碼的行為。

請考慮下列程式碼:

#include <Windows.h> 
 
bool TryLock(__int64* plock) 
{ 
    __int64 lock = *plock; 
    return (lock & 1) && 
        _InterlockedCompareExchange64(plock, lock & ~1, lock) == lock; 
} 

此程式代碼的意圖如下:

  1. plock 指標讀取目前的值。
  2. 檢查這個目前值是否設定了最小有效位。
  3. 如果它確實設定了最小有效位,請清除位,同時保留目前值的其他位。

若要達成此目的,會從指標讀取目前值的複本,plock 並儲存至堆疊變數 locklock 使用三次:

  1. 首先,若要檢查是否已設定最小有效位。
  2. 其次,做為 Comparand 的值 InterlockedCompareExchange64
  3. 最後,在傳回值的比較中 InterlockedCompareExchange64

這假設儲存至堆疊變數的目前值會在函式開頭讀取一次,而且不會變更。 這是必要的,因為目前的值先檢查,然後再嘗試作業,然後明確使用 做為 Comparand 中的 InterlockedCompareExchange64,最後用來比較 傳回值。InterlockedCompareExchange64

不幸的是,先前的程式代碼可以編譯成與原始程式碼預期不同的元件。 使用 Microsoft Visual C++ (MSVC) 編譯程式及 /O1 選項編譯先前的程式代碼,並檢查結果元件程式代碼,以查看取得每個 參考 lock 的鎖定值。 MSVC 編譯程式版本 v19.37 會產生如下所示的元件程序代碼:

plock$ = 8 
bool TryLock(__int64 *) PROC                          ; TryLock, COMDAT 
        mov     r8b, 1 
        test    BYTE PTR [rcx], r8b 
        je      SHORT $LN3@TryLock 
        mov     rdx, QWORD PTR [rcx] 
        mov     rax, QWORD PTR [rcx] 
        and     rdx, -2 
        lock cmpxchg QWORD PTR [rcx], rdx 
        je      SHORT $LN4@TryLock 
$LN3@TryLock: 
        xor     r8b, r8b 
$LN4@TryLock: 
        mov     al, r8b 
        ret     0 
bool TryLock(__int64 *) ENDP                          ; TryLock 

rcx 會保留 參數 plock的值。 元件程式代碼並非在堆疊上建立目前值的複本,而是每次重新讀取值 plock 。 這表示每次讀取值時可能會不同。 這會使開發人員正在執行的清理失效。 值會在驗證其具有最小有效位集之後重新讀取 plock 。 由於在執行此驗證之後會重新讀取,因此新值可能不再設定最小有效位。 在競爭狀況下,此程式代碼的行為可能會如同它成功取得另一個線程鎖定時所指定的鎖定一樣。

只要程式代碼的行為未改變,編譯程式就允許移除或新增記憶體讀取或寫入。 若要防止編譯程式進行這類變更,當您從記憶體讀取值並在變數中快取該值時,強制讀取 volatile 為 。 宣告為 volatile 的物件不會用於特定優化,因為它們的值隨時都可以變更。 產生的程序代碼一律會在要求物件時讀取物件的目前值 volatile ,即使先前的指令要求來自相同物件的值也一樣。 反向也適用於相同的原因。 除非要求, volatile 否則不會再次讀取物件的值。 如需 volatile 的詳細資訊,請參閱volatile。 例如:

#include <Windows.h> 
 
bool TryLock(__int64* plock) 
{ 
    __int64 lock = *static_cast<volatile __int64*>(plock); 
    return (lock & 1) && 
        _InterlockedCompareExchange64(plock, lock & ~1, lock) == lock; 
}

使用與之前相同的 /O1 選項編譯此程序代碼。 產生的元件不再讀取 plock ,以使用 中的 lock快取值。

如需如何修正程序代碼的更多範例,請參閱 範例

程式代碼分析名稱: INTERLOCKED_COMPARE_EXCHANGE_MISUSE

範例

編譯程式可能會將下列程式代碼優化,以多次讀取, plock 而不使用 中的 lock快取值:

#include <Windows.h> 
 
bool TryLock(__int64* plock) 
{ 
    __int64 lock = *plock; 
    return (lock & 1) && 
        _InterlockedCompareExchange64(plock, lock & ~1, lock) == lock; 
}

若要修正此問題,強制讀取為 volatile ,除非明確指示,否則編譯程式不會將程式代碼優化為連續讀取相同的記憶體。 這可防止優化器引入非預期的行為。

將記憶體 volatile 視為 的第一種方法是將目的地地址視為 volatile 指標:

#include <Windows.h> 
 
bool TryLock(volatile __int64* plock) 
{ 
    __int64 lock = *plock; 
    return (lock & 1) && 
        _InterlockedCompareExchange64(plock, lock & ~1, lock) == lock; 
} 

第二個方法是使用 volatile 從目的地位址讀取。 有幾種不同的方式可以執行這項操作:

  • 在取值指標之前,將指標 volatile 轉換成指標
  • volatile從提供的指標建立指標
  • 使用 volatile 讀取協助程式函式。

例如:

#include <Windows.h> 
 
bool TryLock(__int64* plock) 
{ 
    __int64 lock = ReadNoFence64(plock); 
    return (lock & 1) && 
        _InterlockedCompareExchange64(plock, lock & ~1, lock) == lock; 
}

啟發學習法

偵測函式中的 DestinationInterlockedCompareExchange 值或其任何衍生專案是否透過非volatile 讀取載入,然後當做 Comparand 值載入,以強制執行此規則。 不過,它不會明確檢查載入的值是否用來判斷 交換 值。 它假設 交換 值與值相關 Comparand

另請參閱

InterlockedCompareExchange function (winnt.h)
_InterlockedCompareExchange 內部函數