Мне нужна функция, которая (например, SecureZeroMemory из WinAPI) всегда имеет нулевую память и не оптимизируется, даже если компилятор считает, что после этого память никогда не будет доступна. Кажется идеальным кандидатом на неустойчивость. Но у меня возникают некоторые проблемы, связанные с тем, что это работает с 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.
- GCC 4.8.1 до 4.9.2 игнорировать volatile для размеров массивов 1 и 2.
- GCC 5.1 до 5.3 игнорирует volatile для размеров массива 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() приведет к тому, что все версии GCC, кроме 6.1, сгенерируют ожидаемые магазины. Комментируя забор памяти, GCC 6.1 также будет генерировать магазины, хотя только в сочетании с возможной записью из callMeMaybe().
Кто-то также предложил сбросить кеши. Microsoft делает не попытку полностью очистить кеш в "SecureZeroMemory" . Кэш, скорее всего, будет очень недействительным в любом случае, так что это вероятно, не будет большой проблемой. Кроме того, если другая программа пыталась исследовать данные, или если она будет записана в файл страницы, она всегда будет нулевой версией.
Есть также некоторые проблемы с GCC 6.1 с использованием memset() в автономной функции. Компилятор GCC 6.1 на godbolt может сломать сборку, поскольку GCC 6.1, похоже, генерирует обычный цикл (например, 5.3 для godbolt) для автономной функции для некоторых людей. (Прочтите комментарии ответа zwol.)