我需要一个函数(比如 WinAPI 中的 SecureZeroMemory)总是清零内存并且不会被优化掉,即使编译器认为在此之后内存再也不会被访问。看起来是个不稳定的完美候选人。但我在与海湾合作委员会合作时遇到了一些问题。下面是一个示例函数:
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
我测试过的任何其他编译器(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 ()中可能的 write 结合使用。
还有人建议冲洗这些缓存。无论如何,缓存很可能很快就会失效,所以这可能没什么大不了的。此外,如果另一个程序试图探测数据,或者如果它将被写入页面文件,它将始终是零版本。
在独立函数中使用 memset ()的 GCC 6.1也有一些问题。Godbolt 上的 GCC 6.1编译器可能是一个破碎的构建,因为 GCC 6.1似乎为某些人的独立函数生成了一个正常的循环(就像 Godbolt 上的5.3一样)。(阅读 Zwol 回答的评论。)