Reputation: 735
Recently I've got to know WaitOnAddress, WakeByAddressSingle, and Interlocked* family of functions. While making test code as a part of understanding process, I've faced C28112 warning.
Following code is the test code that generates C28112 warning.
#include <iostream>
#include <Windows.h>
#pragma comment(lib, "Synchronization.lib")
using namespace std;
void* g_pThread0 = nullptr;
unsigned long g_ulThread0ID = 0UL;
void* g_pThread1 = nullptr;
unsigned long g_ulThread1ID = 0UL;
short g_sShared = 0i16;
short g_sCompare = 0i16;
unsigned long __stdcall Thread0(void* const _pParameter)
{
int nWaitResult = -1;
short sExchangeResult = -1i16;
while (true)
{
nWaitResult = WaitOnAddress(&g_sShared, &g_sCompare, sizeof(short), INFINITE); // C28112
if (InterlockedOr16(&g_sShared, 0i16) == 1i16) // Check
{
// Do Something
sExchangeResult = InterlockedExchange16(&g_sShared, 0i16);
}
}
return 0UL;
}
unsigned long __stdcall Thread1(void* const _pParameter)
{
short sExchangeResult = -1i16;
while (true)
{
sExchangeResult = InterlockedExchange16(&g_sShared, 1i16);
WakeByAddressSingle(&g_sShared); // C28112
}
return 0UL;
}
int main()
{
g_pThread0 = CreateThread(nullptr, 0UL, Thread0, nullptr, 0UL, &g_ulThread0ID);
g_pThread1 = CreateThread(nullptr, 0UL, Thread1, nullptr, 0UL, &g_ulThread1ID);
while (true)
{
}
return 0;
}
The following two lines are "accessing interlocked variable", I understand that.
nWaitResult = WaitOnAddress(&g_sShared, &g_sCompare, sizeof(short), INFINITE); // C28112
...
WakeByAddressSingle(&g_sShared); // C28112
The question is, how can I remove this warning while using interlocked variable for WaitOnAddress / WakeByAddressSingle?
Currently, I've come up with an answer by declaring g_sShared as pointer.
#include <iostream>
#include <Windows.h>
#pragma comment(lib, "Synchronization.lib")
using namespace std;
void* g_pThread0 = nullptr;
unsigned long g_ulThread0ID = 0UL;
void* g_pThread1 = nullptr;
unsigned long g_ulThread1ID = 0UL;
short* const g_pShared = new short(0i16);
short g_sCompare = 0i16;
unsigned long __stdcall Thread0(void* const _pParameter)
{
int nWaitResult = -1;
short sExchangeResult = -1i16;
while (true)
{
nWaitResult = WaitOnAddress(g_pShared, &g_sCompare, sizeof(short), INFINITE);
if (InterlockedOr16(g_pShared, 0i16) == 1i16) // Check
{
// Do Something
sExchangeResult = InterlockedExchange16(g_pShared, 0i16);
}
}
return 0UL;
}
unsigned long __stdcall Thread1(void* const _pParameter)
{
short sExchangeResult = -1i16;
while (true)
{
sExchangeResult = InterlockedExchange16(g_pShared, 1i16);
WakeByAddressSingle(g_pShared);
}
return 0UL;
}
int main()
{
g_pThread0 = CreateThread(nullptr, 0UL, Thread0, nullptr, 0UL, &g_ulThread0ID);
g_pThread1 = CreateThread(nullptr, 0UL, Thread1, nullptr, 0UL, &g_ulThread1ID);
while (true)
{
}
return 0;
}
This successfully removes warning. However, I feel this approach is a kind of warning removing trick.
[EDIT]
Thanks to the comments by Richard Critten, WBuck, Simon Mourier,
It seems there are 4 options to solve C28112 warning.
1 and 4 seem to be the measures that solve the problem by bypassing warning. 2 and 3 seem to be the measures that solve the problem by satisfying warning .
Even though the warning is "overly cautious", 2 and 3 seem to be essentially problem solving way.
Following is the test code after using InterlockedOr.
#include <iostream>
#include <Windows.h>
#pragma comment(lib, "Synchronization.lib")
using namespace std;
void* g_pThread0 = nullptr;
unsigned long g_ulThread0ID = 0UL;
void* g_pThread1 = nullptr;
unsigned long g_ulThread1ID = 0UL;
short* g_pShared = new short(0i16);
short g_sCompare = 0i16;
unsigned long __stdcall Thread0(void* const _pParameter)
{
int nWaitResult = -1;
short sExchangeResult = -1i16;
while (true)
{
nWaitResult = WaitOnAddress(reinterpret_cast<void*>(InterlockedOr64(reinterpret_cast<long long*>(&g_pShared), 0LL)), &g_sCompare, sizeof(short), INFINITE);
if (InterlockedOr16(g_pShared, 0i16) == 1i16) // Check
{
// Do Something
sExchangeResult = InterlockedExchange16(g_pShared, 0i16);
}
}
return 0UL;
}
unsigned long __stdcall Thread1(void* const _pParameter)
{
short sExchangeResult = -1i16;
while (true)
{
sExchangeResult = InterlockedExchange16(g_pShared, 1i16);
WakeByAddressSingle(reinterpret_cast<void*>(InterlockedOr64(reinterpret_cast<long long*>(&g_pShared), 0LL)));
}
return 0UL;
}
int main()
{
g_pThread0 = CreateThread(nullptr, 0UL, Thread0, nullptr, 0UL, &g_ulThread0ID);
g_pThread1 = CreateThread(nullptr, 0UL, Thread1, nullptr, 0UL, &g_ulThread1ID);
while (true)
{
}
return 0;
}
Code seem surprisingly ugly. Any gentle person help me out please.
Upvotes: 1
Views: 517
Reputation: 13689
I think the warning here is false positive. It should make sure the variable is always accessed atomically, but WaitOnAddress
and WakeByAddress*
access the variable atomically internally.
You can suppress it using #pragma warning(supress:28112)
above the line, or disable temporarily and then restore:
#pragma warning(push)
#pragma warning(disable:28112)
WakeByAddressSingle(...)
#pragma warning(pop)
You can report this false positive to Microsoft if you care (to https://developercommunity.visualstudio.com I guess)
Also if you are using Visual Studio 2019, more convenient way to have atomic wait is to use C++20 std::atomic<T>::wait
/ std::atomic<T>::notify_*
. std::atomic
should not produce this warning, and doesn't allow easy access to the underlying variable to access to it in non-atomic way.
Upvotes: 1