[解決済み] volatile "の定義が揮発性なのか、それともGCCが標準準拠の問題を抱えているのか?
質問
WinAPIのSecureZeroMemoryのように)常にメモリをゼロにし、コンパイラがそのメモリがその後二度とアクセスされないと考えたとしても、最適化されない関数が必要です。volatileの完璧な候補のように思える。しかし、これを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 は volatile を決して無視しません。
- GCC 4.6.4 と 4.7.3 は、配列サイズ 1, 2, 4 の 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) でも、どのコンパイラー バージョンでも、どの配列サイズでも、期待されるストアが生成されます。あるいは、標準の 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でも店舗を生成させます。
誰かがキャッシュをフラッシュすることも提案しました。 マイクロソフトは ではなく は、"SecureZeroMemory" でキャッシュのフラッシュをまったく試みません。 キャッシュはいずれにせよかなり高速に無効化される可能性が高いので、これはおそらく大きな問題ではないでしょう。また、別のプログラムがデータを調査しようとしたり、ページ ファイルに書き込んだりする場合、それは常にゼロにされたバージョンになるはずです。
また、GCC 6.1 がスタンドアロン関数で memset() を使用することについても懸念事項があります。Godbolt 上の GCC 6.1 コンパイラーは壊れたビルドになるかもしれません。GCC 6.1 は、一部の人々にとって、スタンドアロン関数に対して通常のループ (Godbolt 上の 5.3 のように) を生成するようです。(zwolの回答のコメントを読んでください)。
どのように解決するのですか?
GCCの動作
があります。
は適合しているかもしれませんし、適合していない場合でも
volatile
に頼るべきではありません。 C委員会が設計した
volatile
は、メモリマップド・ハードウェア・レジスタと、 異常な制御フロー中に変更される変数(例えば、シグナルハンドラや
setjmp
).
それらに対してのみ信頼性があります。
一般的な "don't optimize this out" 注釈として使用するのは安全ではありません。
特に、この規格は重要な点に関して不明瞭です。 (あなたのコードを C に変換しました。そこで はないはずです。 はないはずです。また、疑わしい最適化の前に起こるであろうインライン化を手動で行い、その時点でコンパイラーが何を見ているかを示しています)。
extern void use_arr(void *, size_t);
void foo(void)
{
char arr[8];
use_arr(arr, sizeof arr);
for (volatile char *p = (volatile char *)arr;
p < (volatile char *)(arr + 8);
p++)
*p = 0;
}
メモリクリアのループは
arr
には揮発性修飾された lvalue を通してアクセスしますが
arr
自体は
ではなく
宣言されている
volatile
. したがって、Cコンパイラがループによって作られたストアは死んだと推測し、ループを完全に削除することは、少なくとも議論の余地なく許されることです。 C Rationale には、委員会が次のようにほのめかしている文章があります。
というのは
を意味するテキストがありますが、私が読んだ限りでは、標準自体は実際にはそのような要求をしていません。
規格が要求していること、していないことの詳細な議論については なぜ揮発性のローカル変数は揮発性の引数と異なる最適化がなされるのでしょうか。また、なぜオプティマイザーは後者からノーオペ・ループを生成するのでしょうか。 , volatileな参照/ポインタを通して宣言された不揮発性オブジェクトにアクセスすることは、そのアクセスに揮発性の規則を与えるのでしょうか? そして GCC バグ 71793 .
委員会の内容については
が考えた
volatile
を検索してください。
C99 の根拠
を検索してください。John Regehr の論文 "。
Volatiles はコンパイルミス
に対するプログラマの期待がどのようなものであるかを詳細に説明しています。
volatile
に対するプログラマの期待が、製品コンパイラによって満たされない可能性があることを詳細に説明しています。LLVM チームによる一連のエッセイ "
すべての C プログラマが未定義の動作について知っておくべきこと
には特に触れていません。
volatile
には特に触れていませんが、現代の C コンパイラがどのように、そしてなぜ
ではなく
ポータブル アセンブラです。
には
実用的
という疑問が湧きます。
volatileZeroMemory
を行うための関数をどのように実装するかという現実的な問題です。 標準が何を要求しているか、あるいは要求するつもりであったかに関係なく、あなたは以下のものを使用できないと仮定するのが最も賢明でしょう。
volatile
を使うことはできないと考えた方が賢明でしょう。 そこで
は
というのも、それが機能しないと他の多くのものを壊してしまうからです。
extern void memory_optimization_fence(void *ptr, size_t size);
inline void
explicit_bzero(void *ptr, size_t size)
{
memset(ptr, 0, size);
memory_optimization_fence(ptr, size);
}
/* in a separate source file */
void memory_optimization_fence(void *unused1, size_t unused2) {}
ただし、絶対に
memory_optimization_fence
はいかなる場合にもインライン化されてはいけません。 それはそれ自身のソースファイルでなければならず、リンク時の最適化の対象になってはいけません。
コンパイラの拡張に依存する他のオプションもあり、それはある状況下で使用可能で、よりタイトなコードを生成できます (そのうちの 1 つは、この回答の以前の版に登場しました) が、どれも普遍的ではありません。
(私が推奨するのは、関数
explicit_bzero
と呼ぶことをお勧めします。なぜなら、この関数は複数のCライブラリでこの名前で利用可能だからです。 この名前には他に少なくとも4つの候補がありますが、それぞれは単一のCライブラリによってのみ採用されています)。
また、たとえこれが動作するようになったとしても、十分でない可能性があることも知っておく必要があります。 特に、以下のことを考慮してください。
struct aes_expanded_key { __uint128_t rndk[16]; };
void encrypt(const char *key, const char *iv,
const char *in, char *out, size_t size)
{
aes_expanded_key ek;
expand_key(key, ek);
encrypt_with_ek(ek, iv, in, out, size);
explicit_bzero(&ek, sizeof ek);
}
AESアクセラレーション命令を持つハードウェアを想定して、もし
expand_key
と
encrypt_with_ek
がインラインの場合、コンパイラは
ek
を完全にベクターレジスターファイル内に保持することができるかもしれません。
explicit_bzero
を呼び出すまでは、それを強制的に
機密データをスタックにコピーする
さらに悪いことに、ベクター レジスタにまだ残っているキーについては何もしません!
関連
-
[解決済み】識別子 "string "は未定義?
-
[解決済み] 非常に基本的なC++プログラムの問題 - バイナリ式への無効なオペランド
-
[解決済み】「Expected '(' for function-style cast or type construction」エラーの意味とは?
-
[解決済み】Visual Studio 2013および2015でC++コンパイラーエラーC2280「削除された関数を参照しようとした」が発生する
-
[解決済み] 非静的データメンバの無効な使用
-
[解決済み] 変数サイズのオブジェクトが初期化されないことがある c++
-
[解決済み] g++とgccの違いは何ですか?
-
[解決済み] 現在のCまたはC++の標準文書はどこにありますか?
-
[解決済み】定義と宣言の違いは何ですか?
-
[解決済み] experimental::filesystem リンカエラー
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】構造体のベクター初期化について
-
[解決済み] エラーが発生する。ISO C++は型を持たない宣言を禁じています。
-
[解決済み] [Solved] Error C1083: Cannot open include file: 'stdafx.h'
-
[解決済み】抽象クラス型の無効なnew-expression
-
[解決済み】変数 '' を抽象型 '' と宣言できない。
-
[解決済み】'cout'は型名ではない
-
[解決済み】エラー:strcpyがこのスコープで宣言されていない
-
[解決済み] 未定義の動作とシーケンスポイント
-
[解決済み】C++17の新機能は何ですか?
-
[解決済み] C言語でgccが一部の文を最適化しないようにするには?