Aracılığıyla paylaş


Uyarı C26837

işlevi func için comparand comp değeri geçici olmayan okuma yoluyla hedef konumdan dest yüklendi.

Bu kural Visual Studio 2022 17.8'e eklendi.

Açıklamalar

InterlockedCompareExchange işlevi ve gibi InterlockedCompareExchangePointertürevleri, belirtilen değerler üzerinde atomik bir karşılaştırma ve değişim işlemi gerçekleştirir. Destination Değer değere Comparand eşitse, exchange değeri tarafından Destinationbelirtilen adreste depolanır. Aksi takdirde hiçbir işlem gerçekleştirilmez. İşlevler, interlocked birden çok iş parçacığı tarafından paylaşılan bir değişkene erişimi eşitlemek için basit bir mekanizma sağlar. Bu işlev, diğer interlocked işlevlere yapılan çağrılara göre atomiktir. İyileştirme kodun davranışını beklenmeyen yollarla değiştirebileceğinden, bu işlevlerin kötüye kullanılması beklediğinizden farklı davranan nesne kodu oluşturabilir.

Aşağıdaki kodu inceleyin:

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

Bu kodun amacı:

  1. İşaretçiden plock geçerli değeri okuyun.
  2. Bu geçerli değerin en az önemli bit kümesine sahip olup olmadığını denetleyin.
  3. En az önemli bit kümesi varsa, geçerli değerin diğer bitlerini korurken biti temizleyin.

Bunu yapmak için, geçerli değerin bir kopyası işaretçidenplock okunur ve bir yığın değişkenine lockkaydedilir. lock üç kez kullanılır:

  1. İlk olarak, en az önemli bitin ayarlı olup olmadığını denetlemek için.
  2. İkincisi, değerine değer InterlockedCompareExchange64olarakComparand.
  3. Son olarak, döndürülen değerin karşılaştırmasında InterlockedCompareExchange64

Bu, yığın değişkenine kaydedilen geçerli değerin işlevin başlangıcında bir kez okunduğunu ve değişmediğini varsayar. Geçerli değer, işlemi denemeden önce önce denetlendiğinden, sonra açıkça içinde InterlockedCompareExchange64olarak kullanıldığından Comparand ve son olarak değerinden InterlockedCompareExchange64döndürülen değeri karşılaştırmak için kullanıldığından bu gereklidir.

Ne yazık ki, önceki kod, kaynak koddan beklediğinizden farklı davranan derlemede derlenebilir. Önceki kodu Microsoft Visual C++ (MSVC) derleyicisi ve /O1 seçeneğiyle derleyin ve başvurulardan her biri lock için kilidin değerinin nasıl alınıp alınıp alınılmaması için sonuç derleme kodunu inceleyin. MSVC derleyici sürümü v19.37 aşağıdakine benzer bir derleme kodu oluşturur:

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 parametresinin plockdeğerini tutar. Derleme kodu, yığındaki geçerli değerin bir kopyasını oluşturmak yerine değeri her seferinde yeniden okuyor plock . Bu, değerin her okunışında farklı olabileceği anlamına gelir. Bu, geliştiricinin gerçekleştirdiği temizleme işlemini geçersiz kılır. Değeri, en az önemli bit kümesine sahip olduğu doğrulandıktan sonra yeniden okunur plock . Bu doğrulama gerçekleştirildikten sonra yeniden okunduğu için, yeni değer artık en az önemli bit kümesine sahip olmayabilir. Bir yarış koşulu altında, bu kod başka bir iş parçacığı tarafından zaten kilitliyken belirtilen kilidi başarıyla almış gibi davranabilir.

Kodun davranışı değiştirilmediği sürece derleyicinin bellek okumalarını veya yazmalarını kaldırmasına veya eklemesine izin verilir. Derleyicinin bu tür değişiklikler yapmasını önlemek için, değeri bellekten okuduğunuzda ve bir değişkende önbelleğe aldığınızda okumaları olmaya volatile zorlayın. olarak volatile bildirilen nesneler, değerleri istedikleri zaman değişebildiğinden belirli iyileştirmelerde kullanılmaz. Oluşturulan kod, önceki bir volatile yönerge aynı nesneden bir değer istese bile, istendiğinde her zaman nesnenin geçerli değerini okur. Tersi aynı nedenle de geçerlidir. nesnenin volatile değeri istenmediği sürece yeniden okunmuyor. hakkında volatiledaha fazla bilgi için bkz volatile. . Örneğin:

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

Bu kodu daha önce olduğu gibi aynı /O1 seçenekle derleyin. Oluşturulan derleme artık içinde önbelleğe alınmış değerin kullanımı için okumaz plocklock.

Kodun nasıl düzeltilebileceğine ilişkin daha fazla örnek için bkz . Örnek.

Kod analizi adı: INTERLOCKED_COMPARE_EXCHANGE_MISUSE

Örnek

Derleyici, içinde önbelleğe alınmış değeri lockkullanmak yerine aşağıdaki kodu birden çok kez okuyacak plock şekilde iyileştirir:

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

Sorunu çözmek için, derleyicinin açıkça belirtilmediği sürece kodu aynı bellekten ardışık olarak okunacak şekilde iyileştirmemesi için okumaları olmaya volatile zorlayın. Bu, iyileştiricinin beklenmeyen davranışlar ortaya çıkarmasını engeller.

Belleği şöyle volatile ele almak için ilk yöntem, hedef adresi işaretçi olarak volatile almaktır:

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

İkinci yöntem, hedef adresten okuma kullanır volatile . Bunu yapmanın birkaç farklı yolu vardır:

  • İşaretçinin başvurularını volatile kaldırmadan önce işaretçiyi işaretçiye atama
  • Sağlanan işaretçiden işaretçi volatile oluşturma
  • Okuma yardımcı işlevlerini kullanma volatile .

Örneğin:

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

Buluşsal yöntemler

Bu kural, işlevin içindeki Destination değerin veya türevlerinden herhangi birinin okunmayan InterlockedCompareExchangevolatile bir değer üzerinden yüklenip yüklenmediğini algılayarak uygulanır ve ardından değer olarak Comparand kullanılır. Ancak, yüklenen değerin exchange değerini belirlemek için kullanılıp kullanılmadığını açıkça denetlemez. Exchange değerinin değerle Comparand ilişkili olduğunu varsayar.

Ayrıca bkz.

InterlockedCompareExchange function (winnt.h)
_InterlockedCompareExchange iç işlevler