(WinAPI의 SecureZeroMemory와 같은) 항상 메모리를 0으로 만들고 컴파일러가 그 후에는 메모리에 다시 액세스하지 않을 것이라고 생각하더라도 최적화되지 않는 함수가 필요합니다. 휘발성에 대한 완벽한 후보처럼 보입니다. 하지만 실제로 GCC와 함께 작동하는 데 몇 가지 문제가 있습니다. 다음은 함수의 예입니다.
void volatileZeroMemory(volatile void* ptr, unsigned long long size)
{
volatile unsigned char* bytePtr = (volatile unsigned char*)ptr;
while (size--)
{
*bytePtr++ = 0;
}
}
충분히 간단합니다. 그러나 GCC가 호출하면 실제로 생성하는 코드는 컴파일러 버전과 실제로 제로화하려는 바이트의 양에 따라 크게 다릅니다. https://godbolt.org/g/cMaQm2
- GCC 4.4.7 및 4.5.3은 휘발성을 무시하지 않습니다.
- GCC 4.6.4 및 4.7.3은 어레이 크기 1, 2 및 4의 휘발성을 무시합니다.
- 4.9.2까지 GCC 4.8.1은 어레이 크기 1과 2의 휘발성을 무시합니다.
- 5.3까지 GCC 5.1은 어레이 크기 1, 2, 4, 8에 대해 휘발성을 무시합니다.
- GCC 6.1은 모든 배열 크기에 대해 무시합니다 (일관성을위한 보너스 포인트).
내가 테스트 한 다른 컴파일러 (clang, icc, vc)는 모든 컴파일러 버전 및 배열 크기로 예상 할 수있는 저장소를 생성합니다. 그래서이 시점에서 나는 이것이 (아주 오래되고 심각한가?) GCC 컴파일러 버그인지, 아니면 이것이 실제로 동작을 준수한다는 것을 부정확하게 표준에서 휘발성의 정의로, 본질적으로 포터블을 작성하는 것을 불가능하게 만드는지 궁금합니다. " SecureZeroMemory "기능?
편집 : 몇 가지 흥미로운 관찰.
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <atomic>
void callMeMaybe(char* buf);
void volatileZeroMemory(volatile void* ptr, std::size_t size)
{
for (auto bytePtr = static_cast<volatile std::uint8_t*>(ptr); size-- > 0; )
{
*bytePtr++ = 0;
}
//std::atomic_thread_fence(std::memory_order_release);
}
std::size_t foo()
{
char arr[8];
callMeMaybe(arr);
volatileZeroMemory(arr, sizeof arr);
return sizeof arr;
}
callMeMaybe ()에서 가능한 쓰기는 6.1을 제외한 모든 GCC 버전이 예상 저장소를 생성하도록합니다. 메모리 펜스에 주석을 달면 GCC 6.1이 저장소를 생성 할 수 있지만 callMeMaybe ()에서 가능한 쓰기와 만 결합됩니다.
누군가는 또한 캐시를 플러시하도록 제안했습니다. Microsoft는 "SecureZeroMemory"에서 캐시를 전혀 플러시 하지 않습니다 . 어쨌든 캐시는 매우 빠르게 무효화 될 가능성이 높으므로 이것은 큰 문제가 아닐 것입니다. 또한 다른 프로그램이 데이터를 조사하려고하거나 페이지 파일에 기록 될 경우 항상 0으로 된 버전이됩니다.
독립형 함수에서 memset ()을 사용하는 GCC 6.1에 대한 몇 가지 우려 사항도 있습니다. Godbolt의 GCC 6.1 컴파일러는 GCC 6.1이 일부 사람들의 독립형 기능에 대해 일반 루프를 생성하는 것처럼 보이기 때문에 빌드가 손상 될 수 있습니다. (zwol의 답변에 대한 의견을 읽으십시오.)
volatile
은 달리 입증되지 않는 한 버그입니다. 하지만 버그 일 가능성이 높습니다.volatile
위험 할 정도로 너무 불명확합니다. 사용하지 마십시오.