경고 C26837
함수
func
에 대한 피비교수comp
의 값이 비휘발성 읽기를 통해 대상 위치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;
}
이 코드의 의도는 다음과 같습니다.
plock
포인터에서 현재 값을 읽습니다.- 이 현재 값에 최하위 비트가 설정되어 있는지 확인합니다.
- 최하위 비트가 설정된 경우 현재 값의 다른 비트를 유지하면서 비트를 지웁니다.
이 작업을 달성하기 위해 plock
포인터에서 현재 값의 복사본을 읽고 스택 변수 lock
에 저장합니다. lock
은(는) 세 번 사용됩니다.
- 먼저 최하위 비트가 설정되어 있는지 확인하기 위해 사용됩니다.
- 둘째,
InterlockedCompareExchange64
에 대한Comparand
값으로 사용됩니다. - 마지막으로
InterlockedCompareExchange64
의 반환 값을 비교하는 데 사용됩니다.
이렇게 하면 스택 변수에 저장된 현재 값을 함수 시작 시 한 번 읽고 변경되지 않는다고 가정합니다. 작업을 시도하기 전에 현재 값을 먼저 확인한 다음 InterlockedCompareExchange64
에서 Comparand
(으)로 명시적으로 사용하고 마지막으로 InterlockedCompareExchange64
의 반환 값을 비교하는 데 사용되기 때문에 이 작업이 필요합니다.
아쉽게도 이전 코드는 소스 코드에서 예상한 것과 다르게 동작하는 어셈블리로 컴파일할 수 있습니다. MSVC(Microsoft Visual C++) 컴파일러 및 /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
옵션을 사용하여 이 코드를 컴파일합니다. 생성된 어셈블리는 더 이상 lock
에 캐시된 값을 사용하기 위해 plock
을(를) 읽지 않습니다.
코드를 수정하는 방법에 대한 추가 예제는 예제를 참조하세요.
코드 분석 이름: INTERLOCKED_COMPARE_EXCHANGE_MISUSE
예시
컴파일러는 lock
에 캐시된 값을 사용하는 대신 plock
을(를) 여러 번 읽도록 다음 코드를 최적화할 수 있습니다.
#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;
}
경험적 학습
이 규칙은 InterlockedCompareExchange
함수의 Destination
값 또는 해당 파생 항목이 volatile
이(가) 아닌 읽기를 통해 로드된 다음 Comparand
값으로 사용되는지 감지하여 적용됩니다. 그러나 로드된 값이 교환 값을 확인하는 데 사용되는지 명시적으로 확인하지는 않습니다. 교환 값이 Comparand
값과 관련이 있다고 가정합니다.
참고 항목
InterlockedCompareExchange
함수(winnt.h)
_InterlockedCompareExchange
내장 함수