web-dev-qa-db-ja.com

スタックの値をゼロにして引数を使いすぎていますか?

私は通常、有用性が提供された瞬間にメモリをゼロにしますが、ヒープに割り当てられたチャンクでのみです。引数とスタック値で同じことをするのは罪(またはやりすぎ)ですか?前もって感謝します。

編集:

これが私の意味の例です:

void foo(int arg0, int *arg1) {
    int local;

    /* ... */

    arg0 = 0; /* passed by value, so zero the copied value */
    arg1 = 0; /* 0 instead of NULL (NULL might not be 0) */
    local = 0; /* MIGHT be on the stack, depending on optimizations */

    /* Edit: now those values should be safe for FUTURE use */
}
2
hiten

C標準では、抽象マシンの観点からコードの動作を指定しています。この仕様は、「スタック」などの概念については認識していません。これは、「自動ストレージ期間」を持つ変数を認識します。つまり、変数が定義されているスコープを離れると、「なくなった」変数を意味します。

C標準の観点から見ると、コード_local = 0;_は、localへの以降のアクセスで値_0_が生成されるという効果のみを持ちます。

さて、標準ではこのように明示的に呼び出されていませんが、 Cには「as-ifルール」 があります。これは、コンパイラが自由にコードを実装できることを意味しますが、「観察可能な動作」が抽象マシンによって「実行」されたときのコードの動作と同じである限り、好きです。

_local = 0;_にはnoの監視可能な動作があります(割り当て後に監視可能な方法でlocalを使用するコードがない場合)。したがって、コンパイラーはその割り当てを破棄できます。最適化設定に関係なく。

このように、このような「ゼロ化」は実際には何もしないことが保証されておらず、コードが乱雑になって読みにくくなるため、罪です。これは動的に割り当てられたメモリにも適用されることに注意してください!

変数を本当にゼロにするにはこの割り当てを「監視可能」にする必要があります。幸い、C標準にはそのための手段が含まれています。つまり、volatile修飾型のオブジェクトへの割り当てを介してです。

したがって、次のような関数を使用して...

_void zero_out(unsigned char volatile * memory, size_t size)
{
  while(size--)
  {
    *memory++ = 0;
  }
}
_

... zero_out(&local, sizeof(local))を呼び出した後、localが保持されている/保持されているメモリ領域(存在する場合)がゼロであることを確認できます。 これにより、コンパイラによるスマートな最適化が無効になるため、メモリ領域が大きい場合は遅くなる可能性があります!

上記の関数よりも優れているのは、標準またはコンパイラーによって提供される関数を使用して同じことを実現することです(最適化されている可能性があるため)。

  • C11には_memset_s_があり、これは最適化されません。 _memset_s_はオプションであり、現在は使用できません。 GCCとClang。
  • MSVCにはSecureZeroMemory

上から私の_zero_out_を実装する他の(潜在的にはより良い)方法もあります:

  • volatile関数ポインター:

    _void * (*volatile memset_here_to_stay)(void *, int, size_t) = memset;
    void zero_out(void * p, size s)
    {
      memset_here_to_stay(p, 0, s);
    }
    _
  • アセンブリで実装された(または別の変換ユニットによって生成された)「外部」関数。注意!リンク時の最適化はこれを非効率にするかもしれません...

4
Daniel Jour