我需要一个函数(例如WinAPI中的SecureZeroMemory)始终将内存归零,并且不会被优化,即使编译器认为此后再也不会访问内存了。似乎是挥发物的理想选择。但是我在将其与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永远不会忽略挥发物。
- 对于数组大小1、2和4,GCC 4.6.4和4.7.3忽略了volatile。
- GCC 4.8.1至4.9.2会忽略数组大小1和2的volatile。
- GCC 5.1直到5.3会忽略数组大小1、2、4、8的volatile
- 对于任何数组大小(一致性的加分点),GCC 6.1都将忽略它。
我测试过的任何其他编译器(clang,icc,vc)都可以生成期望的存储,并且具有任何编译器版本和任何数组大小。所以在这一点上,我想知道,这是一个(相当老而严重的)GCC编译器错误,还是该标准中的volatile定义不精确地表明这实际上是符合规范的行为,因此根本不可能编写可移植的“ 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版本均生成预期的存储。尽管仅与callMeMaybe()的可能写入功能结合使用,但在内存保护区中进行注释也将使GCC 6.1生成存储。
有人还建议刷新缓存。Microsoft根本不尝试刷新“ SecureZeroMemory”中的缓存。无论如何,缓存可能很快就会失效,因此这可能没什么大不了的。同样,如果另一个程序试图探查数据,或者要将其写入页面文件,则该数据将始终为零版本。
在独立功能中使用memset()的GCC 6.1也存在一些问题。Godbolt上的GCC 6.1编译器可能会破坏构建,因为GCC 6.1似乎为某些人的独立功能生成了一个普通循环(就像5.3在Godbolt上一样)。(阅读zwol答案的评论。)
volatile
除非另有证明,否则使用是错误。但是很可能是一个错误。volatile
太少了,以至于很危险-只是不要使用它。