alloca()の使用が良い習慣と見なされないのはなぜですか?
-
06-07-2019 - |
質問
alloca()
は、malloc()
の場合のように、ヒープではなくスタックにメモリを割り当てます。そのため、ルーチンから戻ると、メモリが解放されます。したがって、実際にこれは動的に割り当てられたメモリを解放するという私の問題を解決します。 <=>を介して割り当てられたメモリを解放することは大きな頭痛の種であり、何らかの形で見逃した場合は、あらゆる種類のメモリの問題につながります。
上記の機能にもかかわらず、<=>の使用が推奨されないのはなぜですか?
解決
答えはman
ページにあります(少なくとも Linux ):
戻り値 alloca()関数は、 割り当てられたスペース。もし 割り当ての原因 スタックオーバーフロー、プログラムの動作は未定義です。
これは決して使用すべきではないと言っているわけではありません。私が取り組んでいるOSSプロジェクトの1つはそれを広範囲に使用しており、悪用していない限り(alloca
'巨大な価値がある)、それは問題ありません。 <!> quot;数百バイト<!> quotを過ぎたら、マーク、代わりにmalloc
と友人を使用する時間です。それでも割り当てエラーが発生する可能性がありますが、少なくともスタックを吹き飛ばすのではなく、少なくともエラーの兆候が表示されます。
他のヒント
私が持っていた最も記憶に残るバグの1つは、alloca
を使用するインライン関数を使用することでした。プログラムの実行のランダムなポイントで、スタックオーバーフロー(スタックに割り当てるため)として現れました。
ヘッダーファイル内:
void DoSomething() {
wchar_t* pStr = alloca(100);
//......
}
実装ファイル内:
void Process() {
for (i = 0; i < 1000000; i++) {
DoSomething();
}
}
それで、コンパイラーがインライン化されたDoSomething
関数であり、すべてのスタック割り当てがProcess()
関数内で行われたため、スタックが爆発しました。私の弁護の中で(そして、私は問題を見つけた人ではありませんでした;修正できなかったとき、私は上級開発者の一人に行って泣かなければなりませんでした)、それはまっすぐではありませんでした<=> ATL文字列変換マクロ。
したがって、レッスンは次のとおりです。インライン化される可能性があると思われる関数では<=>を使用しないでください。
古い質問ですが、可変長配列に置き換える必要があるとは誰も言及していませんでした。
char arr[size];
の代わりに
char *arr=alloca(size);
標準C99にあり、多くのコンパイラのコンパイラ拡張機能として存在していました。
alloca()は、標準のローカル変数を使用できない場合に非常に便利です。そのサイズは実行時に決定する必要があり、 alloca()から取得したポインタは、この関数が戻った後に使用されないことを絶対に保証します。
次の場合はかなり安全になります
- ポインタ、またはそれを含むものを返さないでください。
- ヒープに割り当てられた構造にはポインターを保存しない
- 他のスレッドにポインターを使用させない
本当の危険性は、誰かが後でこれらの条件に違反する可能性から生じます。それを念頭に置いて、テキストをフォーマットする関数にバッファを渡すのに最適です:)
このニュースグループの投稿に記載されているように、いくつかの理由がありますalloca
を使用することが困難で危険と見なされる理由:
- すべてのコンパイラが<=>をサポートしているわけではありません。
- 一部のコンパイラは<=>の意図した動作を異なる方法で解釈するため、それをサポートするコンパイラ間でも移植性は保証されません。
- 一部の実装にはバグがあります。
1つの問題は、広くサポートされているものの、標準ではないことです。他の条件が同じであれば、一般的なコンパイラ拡張機能ではなく、常に標準関数を使用します。
まだallocaの使用は推奨されていません、なぜですか?
私はそのようなコンセンサスを認識していません。多くの強力なプロ。短所:
- C99は可変長の配列を提供します。固定長の配列と一貫性があり、全体的に直感的であるため、表記法が優先的に使用されることがよくあります
- 多くのシステムでは、ヒープに比べてスタックで使用できる全体的なメモリ/アドレス空間が少ないため、プログラムはメモリ枯渇(スタックオーバーフローによる)の影響を受けやすくなります。これは、良いか悪いと見なされる場合があります。事-スタックがヒープのように自動的に成長しない理由の1つは、制御不能なプログラムがマシン全体に大きな悪影響を与えないようにすることです
- よりローカルなスコープ(
while
やfor
ループなど)または複数のスコープで使用される場合、メモリは反復/スコープごとに蓄積され、関数が終了するまで解放されません:これは定義された通常の変数とは対照的です制御構造の範囲内(たとえば、for {int i = 0; i < 2; ++i) { X }
はXで要求されたalloca
-edメモリを蓄積しますが、固定サイズの配列のメモリは反復ごとにリサイクルされます)。 - 現代のコンパイラは通常、
inline
を呼び出すmalloc
関数を実行しませんが、それらを強制すると、呼び出し元のコンテキストでWonderfulObject_DestructorFree(ptr)
が発生します(つまり、呼び出し元が戻るまでスタックは解放されません) - かなり前に
WonderfulObject* p = WonderfulObject_AllocConstructor();
移植性のない機能/ハックから標準化された拡張機能に移行しましたが、否定的な認識が続く場合があります - 存続期間は関数スコープにバインドされます。これは、
p
の明示的な制御よりもプログラマに適している場合とそうでない場合があります -
WonderfulObject_AllocConstructor
を使用することで、割り当て解除について考えることができます-ラッパー関数(たとえばfree
)で管理されている場合、関数は実装のクリーンアップ操作(ファイル記述子のクローズ、内部ポインターの解放、クライアントのコードを明示的に変更せずにログを記録します):一貫して採用するのが良いモデルである場合があります- この疑似オブジェクト指向プログラミングでは、
alloca()
のようなものが必要です。これは、<!> quot; constructor <!> quot; <!> quot; constructor <!> quotではなく、realloc
-edメモリを返す関数です(関数が<=>に格納される値を返した後もメモリが割り当てられたままになるため)。 <=>を使用します- マクロバージョン<=>はこれを達成できますが、<!> quot;マクロは悪<< >> quot;互いに競合し、マクロ以外のコードと競合し、意図しない置換を引き起こし、結果として診断が困難な問題が発生する可能性があるという点で
- 欠落している<=>操作はValGrind、Purifyなどによって検出できますが、欠落している<!> quot; destructor <!> quot;呼び出しを常に検出できるわけではありません-使用目的の実施という点で非常にわずかな利点です。一部の<=>実装(GCCなど)は<=>にインラインマクロを使用するため、メモリ使用量診断ライブラリの実行時置換は<=> / <=> / <=>のようには不可能です(例:電気柵)
- この疑似オブジェクト指向プログラミングでは、
- 一部の実装には微妙な問題があります。たとえば、Linuxマンページ:
多くのシステムでは、alloca()によって予約されたスタックスペースが関数引数のスペースの中央のスタックに表示されるため、関数呼び出しの引数リスト内でalloca()を使用することはできません。
この質問にはCというタグが付いていますが、C ++プログラマーとして、C ++を使用して<=>の潜在的なユーティリティを説明すると思いました:以下のコード(およびこちらideone )は、ヒープが割り当てられるのではなく、スタックが割り当てられた(関数の戻り値に関連付けられた存続期間を持つ)異なるサイズの多態型を追跡するベクトルを作成します。
#include <alloca.h>
#include <iostream>
#include <vector>
struct Base
{
virtual ~Base() { }
virtual int to_int() const = 0;
};
struct Integer : Base
{
Integer(int n) : n_(n) { }
int to_int() const { return n_; }
int n_;
};
struct Double : Base
{
Double(double n) : n_(n) { }
int to_int() const { return -n_; }
double n_;
};
inline Base* factory(double d) __attribute__((always_inline));
inline Base* factory(double d)
{
if ((double)(int)d != d)
return new (alloca(sizeof(Double))) Double(d);
else
return new (alloca(sizeof(Integer))) Integer(d);
}
int main()
{
std::vector<Base*> numbers;
numbers.push_back(factory(29.3));
numbers.push_back(factory(29));
numbers.push_back(factory(7.1));
numbers.push_back(factory(2));
numbers.push_back(factory(231.0));
for (std::vector<Base*>::const_iterator i = numbers.begin();
i != numbers.end(); ++i)
{
std::cout << *i << ' ' << (*i)->to_int() << '\n';
(*i)->~Base(); // optionally / else Undefined Behaviour iff the
// program depends on side effects of destructor
}
}
その他の答えはすべて正しいです。ただし、alloca()
を使用して割り当てたいものがかなり小さい場合は、malloc()
などを使用するよりも速くて便利な優れた手法だと思います。
つまり、alloca( 0x00ffffff )
は危険であり、char hugeArray[ 0x00ffffff ];
とまったく同じようにオーバーフローを引き起こす可能性があります。慎重かつ合理的であり、あなたは大丈夫です。
誰もが、スタックオーバーフローによる未定義の動作の可能性がある大きなことを既に指摘していますが、Windows環境には、構造化例外(SEH)とガードページを使用してこれをキャッチする優れたメカニズムがあることに言及する必要があります。スタックは必要に応じて大きくなるだけなので、これらのガードページは割り当てられていない領域に存在します。 (スタックをオーバーフローさせることで)それらに割り当てると、例外がスローされます。
このSEH例外をキャッチし、_resetstkoflwを呼び出してスタックをリセットし、陽気な方法で続行できます。それは理想的ではありませんが、ファンに何かが当たったときに何かが間違っていることを少なくとも知るための別のメカニズムです。 * nixには、私が知らない似たようなものがあるかもしれません。
allocaをラップして内部的に追跡することにより、最大割り当てサイズを制限することをお勧めします。本当にハードコアな場合は、関数の上部にスコープセントリーを投げて、関数スコープ内のすべてのalloca割り当てを追跡し、これをプロジェクトで許可されている最大量に対して健全性チェックします。
また、メモリリークを許可しないことに加えて、allocaは非常に重要なメモリの断片化を引き起こしません。 allocaを賢く使用するのが悪い習慣だとは思いません。これは基本的にすべてに当てはまります。 :-)
alloca()は素晴らしく効率的ですが、深く壊れています。
- 壊れたスコープの動作(ブロックスコープではなく関数スコープ)
- mallocと矛盾する使用( alloca() -tedポインターは解放しないでください。したがって、ポインターがどこから来ているかを free()のみで追跡する必要があります。 malloc()) で取得したもの
- インライン化も使用すると動作が悪くなります(呼び出し先がインライン化されているかどうかに応じて、スコープが呼び出し側関数に移動することがあります)。
- スタック境界チェックなし
- 失敗した場合の未定義の動作(malloc ...のようにNULLを返しません。スタック境界をチェックしないため、失敗は何を意味しますか...)
- ANSI標準ではありません
ほとんどの場合、ローカル変数とメジャーサイズを使用して置き換えることができます。大きいオブジェクトに使用する場合、通常はヒープに置く方が安全です。
本当にCが必要な場合は、VLAを使用できます(C ++にはvlaはありませんが、非常に悪いです)。それらは、スコープの動作と一貫性に関してalloca()よりもはるかに優れています。ご覧のとおり、 VLA は一種の alloca()が適切に作成されています。
もちろん、必要なスペースの大部分を使用するローカル構造または配列の方が優れており、そのような大部分のヒープ割り当てがない場合は、通常のmalloc()を使用するのが適切です。 alloca()または VLA。
のいずれかを本当に必要とする正気な使用例はありません。この<!> quot; old <!> quot;に対する興味深い回答がたくさんあります。質問、比較的新しい回答もありますが、これに言及するものは見つかりませんでした。...
適切に、注意して使用すると、一貫した
alloca()
の使用 (おそらくアプリケーション全体)小さな可変長の割り当てを処理する (または利用可能な場合はC99 VLA)は、全体的なスタックを下げる可能性があります 特大を使用して同等の実装よりも成長 固定長のローカル配列。したがって、慎重に使用すれば、<=>は スタックに適している かもしれません。
その引用を見つけました.... OK、その引用を作成しました。しかし、実際に考えてみてください。...
@j_random_hackerは、他の回答の下にある彼のコメントで非常に正しいです:特大のローカル配列を支持して<=>の使用を避けても、スタックオーバーフローからプログラムが安全になるわけではありません(コンパイラが、アップグレードする必要がある場合は<=>を使用するか、ループ内で<=>を使用する場合を除き、ループ内では<=>を使用しないでください。
デスクトップ/サーバー環境と組み込みシステムに取り組んできました。多くの組み込みシステムは、ヒープをまったく使用しません(それらをサポートするためにリンクすることすらありません)。これは、動的に割り当てられたメモリが、アプリケーションのメモリリークのリスクにより悪であるという認識を含む理由からです。一度に何年もリブートするか、アプリケーションがヒープを誤ったメモリ枯渇の点まで断片化しないことが確実ではないため、動的メモリは危険であるというより合理的な正当化。そのため、組み込みプログラマには選択肢がほとんどありません。
<=>(またはVLA)は、ジョブに最適なツールかもしれません。
時間を見てきました<!> amp;再び、プログラマーがスタックに割り当てられたバッファーを作成します。深くネストされたコールツリーでは、その(反?)パターンを繰り返し使用すると、スタックが誇張されて使用されます。 (コールツリーが20レベルの深さを想像してください。さまざまな理由で各レベルで、関数は1024バイトのバッファを盲目的に過剰に割り当てます<!> quot;ちょうど安全に<!> quot;一般的には16または代わりに、<=>またはVLAを使用し、関数が必要とするだけのスタックスペースを割り当てて、スタックに不必要な負担をかけないようにすることもできます。コールツリー内の1つの関数が通常よりも大きい割り当てを必要とし、コールツリー内の他の関数がまだ通常の小さな割り当てを使用し、アプリケーションスタック全体の使用量がすべての関数がローカルバッファを盲目的に過剰割り当てした場合よりもはるかに少ないことを願っています。
ただし、<=> ...
の使用を選択した場合このページの他の回答に基づいて、VLAは安全であるように思われます(ループ内から呼び出された場合、スタック割り当てを複合しません)が、<=>を使用している場合は、使用しないように注意してくださいループ内で、別の関数のループ内で呼び出される可能性がある場合は、関数をインライン化できないようにします。
理由は次のとおりです。
char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;
誰もがこのコードを書くわけではありませんが、alloca
に渡すサイズ引数は、ほぼ確実に何らかの入力に由来します。結局のところ、サイズが入力に基づいていない場合、または大きくなる可能性がない場合、小さな固定サイズのローカルバッファーを宣言しないのはなぜですか?
<=>やC99 vlasを使用する実質的にすべてのコードには、クラッシュ(幸運な場合)または特権の侵害(幸運でない場合)につながる深刻なバグがあります。
alloca()
がmalloc()
よりも特に危険な場所はカーネルです。典型的なオペレーティングシステムのカーネルには、ヘッダーの1つにハードコードされた固定サイズのスタックスペースがあります。アプリケーションのスタックほど柔軟ではありません。保証されていないサイズで<=>を呼び出すと、カーネルがクラッシュする可能性があります。
特定のコンパイラーは、カーネルコードのコンパイル中にオンにする必要がある特定のオプションの下で<=>(さらにはVLAも)の使用を警告します-ここでは、ハードによって修正されないヒープにメモリを割り当てる方が良いコード化された制限。
alloca
で割り当てられたブロックを誤って書き込んだ場合(たとえば、バッファオーバーフローが原因で)、関数のリターンアドレスは<!>にあるため上書きされます。 > quot; above <!> quot;スタック上、つまり、割り当てられたブロックの後。
この結果は2つあります:
-
プログラムは見事にクラッシュし、クラッシュした理由や場所を特定することはできません(フレームポインターが上書きされるため、スタックがランダムアドレスに巻き戻される可能性が高い)。
-
悪意のあるユーザーが特別なペイロードを作成してスタックに配置し、最終的に実行される可能性があるため、バッファオーバーフローが何倍も危険になります。
対照的に、ヒープ上のブロックを超えて書き込む場合、<!> quot; just <!> quot;ヒープ破損を取得します。プログラムはおそらく予期せず終了しますが、スタックを適切に巻き戻すため、悪意のあるコードが実行される可能性が低くなります。
alloca
の落とし穴の1つは、longjmp
が巻き戻すことです。
つまり、コンテキストをsetjmp
で保存し、次にjmp_buf
いくらかのメモリ、次に<=>でコンテキストに保存すると、<=>メモリが失われる可能性があります(通知なし)。スタックポインターは元の場所に戻っているため、メモリは予約されていません。関数を呼び出すか、別の<=>を実行すると、元の<=>を上書きします。
明確にするために、ここで具体的に言及しているのは、<=>が発生した関数から<=>が返されない状況です!むしろ、関数は<=>でコンテキストを保存します。次に<=>でメモリを割り当て、最後にそのコンテキストに対してlongjmpが実行されます。その関数の<=>メモリはすべて解放されません。 <=>以降に割り当てられたすべてのメモリ。もちろん、私は観察された行動について話している。私が知っている<=>のような要件は文書化されていません。
ドキュメントの焦点は通常、<=>メモリがブロックではなく機能のアクティベーションに関連付けられているという概念にあります。 <=>の複数の呼び出しは、関数が終了したときにすべて解放されるスタックメモリをさらに取得するだけです。そうではない;メモリは実際にプロシージャコンテキストに関連付けられています。コンテキストが<=>で復元されると、以前の<=>状態も復元されます。これは、スタックポインターレジスタ自体が割り当てに使用され、(必要に応じて)<=>に保存および復元されるためです。
ちなみに、これは、そのように機能する場合、<=>で割り当てられたメモリを意図的に解放するためのもっともらしいメカニズムを提供します。
バグの根本原因としてこれに遭遇しました。
誰もこれに言及しているとは思いません:関数でallocaを使用すると、コンパイラは関数のスタックフレームのサイズを知ることができないため、関数に適用できない最適化を妨げたり無効にしたりします。
たとえば、Cコンパイラによる一般的な最適化は、関数内でのフレームポインターの使用を排除することであり、代わりにスタックポインターを基準にしてフレームアクセスが行われます。そのため、一般的な使用のためのもう1つのレジスタがあります。ただし、関数内でallocaが呼び出された場合、spとfpの違いは関数の一部で不明になるため、この最適化は実行できません。
その使用の希少性と標準機能としての日陰の状態を考えると、コンパイラ設計者は、すべての最適化を無効にする可能性があります。 allocaで動作させるための少しの努力以上。
更新: 可変長のローカル配列がCおよびC ++に追加されており、これらはallocaとしてコンパイラに非常によく似たコード生成の問題を示しているため、「使用の希少性と怪しげなステータス」は基礎となるメカニズムには適用されません。しかし、allocaまたはVLAのいずれかを使用すると、それらを使用する関数内でコード生成が危険にさらされる傾向があると、私はまだ疑っています。コンパイラ設計者からのフィードバックを歓迎します。
残念なことに、本当にすごいalloca()
がほとんどすごいtccにありません。 Gccにはmalloc()
があります。
-
それはそれ自身の破壊の種をwsきます。デストラクタとしてreturnを使用します。
-
realloc()
と同様に、失敗時に無効なポインターを返します。これは、MMUを備えた最新のシステムでセグメンテーション違反を引き起こします(できれば再起動せずに再起動します)。 -
自動変数とは異なり、実行時にサイズを指定できます。
これは再帰でうまく機能します。静的変数を使用して末尾再帰に似たものを実現し、他のいくつかを使用して各反復に情報を渡すことができます。
深く押し込みすぎると、セグメンテーション違反が発生します(MMUがある場合)。
システムがメモリ不足になると、<=>はNULL(割り当てられている場合はセグメンテーション違反も発生します)を返すため、<=>は提供しません。つまりできることは、保釈するか、または何らかの方法で割り当てることです。
<=>を使用するには、グローバルを使用し、NULLを割り当てます。ポインターがNULLでない場合は、<=>を使用する前に解放します。
既存のデータをコピーする場合は、一般的なケースとして<=>を使用することもできます。 <=>の後にコピーまたは連結する場合は、解決する前にポインターを確認する必要があります。
プロセスで使用できるスタック領域は限られています-malloc()
で使用可能なメモリ量よりもはるかに少ないです。
alloca()
を使用すると、スタックオーバーフローエラーが発生する可能性が大幅に高まります(幸運な場合、またはそうでない場合は不可解なクラッシュ)。
あまりきれいではありませんが、パフォーマンスが本当に重要な場合は、スタック上のスペースを事前に割り当てることができます。
すでにメモリブロックの最大サイズが必要であり、オーバーフローチェックを維持したい場合は、次のように実行できます。
void f()
{
char array_on_stack[ MAX_BYTES_TO_ALLOCATE ];
SomeType *p = (SomeType *)array;
(...)
}
実際、allocaはスタックの使用を保証していません。 実際、alccaのgcc-2.95実装は、malloc自体を使用してヒープからメモリを割り当てます。また、その実装にはバグがあり、gotoをさらに使用してブロック内で呼び出すと、メモリリークが発生し、予期しない動作が発生する可能性があります。使用しないでくださいと言ってはいけませんが、場合によってはallocaが再リーブするよりもオーバーヘッドが大きくなることがあります。
alloca関数は素晴らしく、すべての否定論者は単にFUDを広めています。
void foo()
{
int x = 50000;
char array[x];
char *parray = (char *)alloca(x);
}
Arrayとparrayはまったく同じで、まったく同じリスクです。一方が他方より優れていると言うことは、技術的な選択ではなく、構文上の選択です。
スタック変数とヒープ変数の選択に関しては、スコープ内の有効期間を持つ変数に対してスタックオーバーヒープを使用する長時間実行プログラムには多くの利点があります。ヒープの断片化を回避し、未使用の(使用不可能な)ヒープスペースでプロセススペースを増やすことを回避できます。クリーンアップする必要はありません。プロセスのスタック割り当てを制御できます。
なぜこれが悪いのですか?
IMHO、誰もがスタックサイズの制限を使い果たすことを恐れているため、allocaは悪い習慣と見なされます。
このスレッドと他のリンクを読むことで多くのことを学びました:
- https://unix.stackexchange.com/questions/63742/what-is -automatic-stack-expansion
- プログラムのスタック割り当て制限Linux 32ビットマシン
- ulimit -s
主にallocaを使用して、msvcおよびgccでプレーンCファイルを変更せずにコンパイルできるようにします。C89スタイル、#ifdef _MSC_VERなどはありません。
ありがとう!このスレッドで私はこのサイトにサインアップしました:)
私の意見では、利用可能なalloca()は、制約された方法でのみ使用されるべきです。 <!> quot; goto <!> quot;の使用と非常によく似ていますが、他の多くの合理的な人々はalloca()の使用だけでなく、その存在にも強い嫌悪感を抱いています。
組み込み用途の場合、スタックサイズが既知であり、割り当てサイズの規則と分析によって制限を課すことができ、コンパイラをC99 +をサポートするようにアップグレードできない場合、alloca()の使用は問題ありません。 'それを使用することが知られています。
利用可能な場合、VLAにはalloca()よりもいくつかの利点がある場合があります:コンパイラーは、配列スタイルのアクセスが使用されるときに範囲外のアクセスをキャッチするスタック制限チェックを生成できます(コンパイラーがこれを行うかどうかはわかりませんが、コードを分析して、配列アクセス式が適切にバインドされているかどうかを判断できます。自動車、医療機器、航空電子工学などの一部のプログラミング環境では、この分析は、自動(スタック上)と静的割り当て(グローバルまたはローカル)の両方の固定サイズアレイに対しても行わなければならないことに注意してください。
データを格納し、スタックにアドレス/フレームポインターを返すアーキテクチャ(私が知っていることから、それはすべてです)では、スタックに割り当てられた変数は、変数のアドレスが取得され、入力がチェックされないため危険です値はあらゆる種類のいたずらを許可する可能性があります。
埋め込みスペースでは移植性はそれほど懸念されませんが、慎重に制御された状況以外でalloca()を使用することに対する良い議論です。
埋め込みスペースの外側で、私はalloca()を主に効率化のためにロギングおよびフォーマット関数の内部で使用し、非再帰的な字句スキャナーで、一時的な構造(alloca()を使用して割り振られた)がトークン化および分類中に作成され、 (malloc()を介して割り当てられた)永続オブジェクトは、関数が戻る前に読み込まれます。小さな一時構造にalloca()を使用すると、永続オブジェクトが割り当てられたときの断片化が大幅に減少します。
ここでの答えの大部分はポイントを逃しています:_alloca()
の使用が単にスタックに大きなオブジェクトを保存するよりも潜在的に悪い理由があります。
自動ストレージと<=>の主な違いは、後者には追加の(深刻な)問題があることです:割り当てられたブロックはコンパイラーによって制御されないので、コンパイラーには方法がありません最適化またはリサイクルします。
比較:
while (condition) {
char buffer[0x100]; // Chill.
/* ... */
}
with:
while (condition) {
char* buffer = _alloca(0x100); // Bad!
/* ... */
}
後者の問題は明らかです。
これについては誰も言及していないと思いますが、allocaには、mallocに必ずしも存在しない重大なセキュリティ問題もあります(ただし、これらの問題は、動的または非スタックベースの配列でも発生します)。メモリーはスタックに割り当てられるため、バッファーのオーバーフロー/アンダーフローは、mallocのみを使用する場合よりもはるかに深刻な結果になります。
特に、関数の戻りアドレスはスタックに保存されます。この値が破損した場合、コードはメモリの実行可能な領域に移動する可能性があります。コンパイラは、これを困難にするために(特にアドレスレイアウトをランダム化することにより)多大な労力を費やしています。ただし、戻り値が破損している場合は最善のケースはSEGFAULTであるため、これは単なるスタックオーバーフローよりも明らかに悪いですが、ランダムなメモリの実行を開始したり、最悪の場合はプログラムのセキュリティを損なうメモリの一部の領域を開始することもあります。