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