プログラミング、リファクタリング、そしてすべてにおける究極の疑問: No. 7

その他インテル® DPC++/C++ コンパイラー特集

この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。


7. ループ内で alloca() 関数を呼び出さない

この問題は、Pixie プロジェクトで見つかりました。このエラーは、次の PVS-Studio 診断によって検出されます。

V505 The ‘alloca’ function is used inside the loop. This can quickly overflow stack. (V505 ‘alloca’ 関数がループ内で使用されています。これは、スタック・オーバーフローを引き起こします。)

inline  void  triangulatePolygon(....) {
  ...
  for (i=1;i<nloops;i++) {
    ...
    do {
      ...
      do {
        ...
        CTriVertex *snVertex =
          (CTriVertex *) alloca(2*sizeof(CTriVertex));
        ...
      } while(dVertex != loops[0]);
      ...
    } while(sVertex != loops[i]);
    ...
  }
  ...
}

説明

alloca(size_t) 関数は、スタックを使用してメモリーを割り当てます。alloca() で割り当てられたメモリーは、関数の終了時に解放されます。

通常、プログラムに割り当てられるスタックメモリーはそれほど大きくありません。Visual C++* でプロジェクトを作成する場合、デフォルトのスタック・メモリー・サイズはわずか 1MB です。そのため、alloca() 関数をループ内で使用すると、利用可能なすべてのスタックメモリーをすぐに消費してしまいます。

上記の例では、3 レベルの入れ子構造のループが使用されています。そのため、大きな多角形に三角法を適用すると、スタック・オーバーフローが発生します。

また、A2W などのマクロには alloca() 関数の呼び出しが含まれているため、ループでこれらのマクロを使用することは安全ではありません。

前述のとおり、デフォルトでは Windows* プログラムは 1MB のスタックを使用します。この値は、プロジェクト設定にある [スタックのサイズの設定] と [スタックのコミット サイズ] で変更できます。詳細は、「/STACK (スタック割り当て)」を参照してください。ただし、スタックサイズを増やすことは、スタック・オーバーフローが発生するタイミングを先延ばしするだけであって、問題の解決にはなりません。

推奨事項

ループ内では、alloca() 関数を呼び出さないようにします。ループで一時バッファーを割り当てる必要がある場合、次のいずれかの方法を使用します。

  1. あらかじめメモリーを割り当てておき、1 つのバッファーをすべての操作で使用します。毎回異なるサイズのバッファーが必要な場合、一番大きなサイズでメモリーを割り当てます。必要なメモリーサイズが不明な場合は、2 つ目の方法を使用します。
  2. ループの本体を別の関数にします。そうすることで、反復ごとにバッファーの作成と破棄が行われます。これも困難な場合は、3 つ目の方法を使用します。
  3. alloca()malloc() 関数または new 演算子に置き換えるか、std::vector などのクラスを使用します。この場合、メモリー割り当てにはより多くの時間がかかります。malloc/new を使用する場合、解放についても考慮する必要があります。これで、大規模なデータでプログラムを実行しても、スタック・オーバーフローが発生しなくなります。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

タイトルとURLをコピーしました