この記事は、インテル® デベロッパー・ゾーンに公開されている「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 つのバッファーをすべての操作で使用します。毎回異なるサイズのバッファーが必要な場合、一番大きなサイズでメモリーを割り当てます。必要なメモリーサイズが不明な場合は、2 つ目の方法を使用します。
- ループの本体を別の関数にします。そうすることで、反復ごとにバッファーの作成と破棄が行われます。これも困難な場合は、3 つ目の方法を使用します。
- alloca() を malloc() 関数または new 演算子に置き換えるか、std::vector などのクラスを使用します。この場合、メモリー割り当てにはより多くの時間がかかります。malloc/new を使用する場合、解放についても考慮する必要があります。これで、大規模なデータでプログラムを実行しても、スタック・オーバーフローが発生しなくなります。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。