(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
- GCC4.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()からの可能な書き込みにより、6.1を除くすべてのGCCバージョンで期待されるストアが生成されます。メモリフェンスでコメントすると、GCC 6.1でもストアが生成されますが、callMeMaybe()からの可能な書き込みとの組み合わせのみです。
誰かがキャッシュをフラッシュすることも提案しました。Microsoftは、「SecureZeroMemory」のキャッシュをフラッシュしようとはしません。とにかくキャッシュはかなり速く無効になる可能性が高いので、これはおそらく大したことではありません。また、別のプログラムがデータをプローブしようとした場合、またはデータがページファイルに書き込まれる場合は、常にゼロバージョンになります。
スタンドアロン関数でmemset()を使用するGCC6.1についてもいくつかの懸念があります。一部の人々にとって、GCC 6.1はスタンドアロン関数の通常のループ(godboltでの5.3のように)を生成するように見えるため、godbolt上のGCC6.1コンパイラはビルドが壊れている可能性があります。(zwolの回答のコメントを読んでください。)
volatile
特に証明されない限り、IMHOの使用はバグです。しかし、おそらくバグです。volatile
危険であるほど指定が不十分です-使用しないでください。