alloca()
の場合のように、ヒープではなくスタックにメモリを割り当てますmalloc()
。したがって、ルーチンから戻ると、メモリが解放されます。したがって、実際には、これにより、動的に割り当てられたメモリを解放するという私の問題が解決されます。割り当てられmalloc()
たメモリの解放は大きな頭痛の種であり、何らかの理由で見落とされた場合、あらゆる種類のメモリの問題が発生します。
上記の機能にもかかわらず、なぜ使用がalloca()
推奨されないのですか?
alloca()
の場合のように、ヒープではなくスタックにメモリを割り当てますmalloc()
。したがって、ルーチンから戻ると、メモリが解放されます。したがって、実際には、これにより、動的に割り当てられたメモリを解放するという私の問題が解決されます。割り当てられmalloc()
たメモリの解放は大きな頭痛の種であり、何らかの理由で見落とされた場合、あらゆる種類のメモリの問題が発生します。
上記の機能にもかかわらず、なぜ使用がalloca()
推奨されないのですか?
答えはman
ページのすぐそこにあります(少なくともLinuxでは):
戻り値alloca()関数は、割り当てられたスペースの先頭へのポインターを返します。割り当てによってスタックオーバーフローが発生した場合、プログラムの動作は未定義です。
それは決して使用されるべきではないということではありません。私が取り組んでいるOSSプロジェクトの1つはそれを広範囲に使用しており、それを悪用しない限り(alloca
'巨大な価値を持っている)、それは問題ありません。「数百バイト」のマークを超えたらmalloc
、代わりに友人を使用します。それでも割り当ての失敗が発生する可能性がありますが、少なくとも、スタックを吹き飛ばすだけでなく、失敗の兆候が見られます。
私が抱えていた最も記憶に残るバグの1つは、を使用したインライン関数に関するものalloca
でした。これは、プログラムの実行のランダムなポイントで(スタックに割り当てられるため)スタックオーバーフローとして現れました。
ヘッダーファイル:
void DoSomething() {
wchar_t* pStr = alloca(100);
//......
}
実装ファイル:
void Process() {
for (i = 0; i < 1000000; i++) {
DoSomething();
}
}
つまり、コンパイラのインラインDoSomething
関数が発生し、すべてのスタック割り当てがProcess()
関数内で発生し、スタックが爆発しました。私の弁護では(そして私は問題を見つけた人ではありませんでした;私がそれを修正できなかったとき私は上級開発者の1人に行って泣かなければなりませんでした)、それはまっすぐではありませんでしたalloca
、それはATL文字列変換の1つでしたマクロ。
したがって、レッスンは-alloca
インライン化される可能性があると思われる関数では使用しないでください。
古い質問ですが、可変長配列に置き換える必要があるとは誰も言及していません。
char arr[size];
それ以外の
char *arr=alloca(size);
これは標準のC99にあり、多くのコンパイラでコンパイラ拡張として存在していました。
alloca()は、実行時にサイズを決定する必要があるため、標準のローカル変数を使用できない場合に非常に便利です 。また、この関数が。を返した後は、alloca()から取得したポインターが使用されないことを絶対に保証できます。
あなたがするならあなたはかなり安全になることができます
本当の危険は、他の誰かが後でこれらの条件に違反する可能性から来ています。それを念頭に置いて、テキストをフォーマットする関数にバッファを渡すのに最適です:)
このニュースグループの投稿に記載されているように、使用alloca
が困難で危険であると見なされる理由はいくつかあります。
alloca
。alloca
異なる方法で解釈するため、それをサポートするコンパイラ間でも移植性は保証されません。1つの問題は、広くサポートされていますが、標準ではないことです。他の条件が同じであれば、私は常に、一般的なコンパイラ拡張ではなく、標準関数を使用します。
それでもアロカの使用は推奨されていません、なぜですか?
私はそのようなコンセンサスを認識していません。強力なプロがたくさん。いくつかの短所:
while
またはfor
ループなど)または複数のスコープで使用される場合、メモリは反復/スコープごとに蓄積され、関数が終了するまで解放されません。これは、制御構造のスコープで定義された通常の変数とは対照的です(例:Xで要求された-edメモリfor {int i = 0; i < 2; ++i) { X }
を蓄積しますが、固定サイズの配列のメモリは反復ごとにリサイクルされます)。alloca
inline
最近のコンパイラは通常、を呼び出す関数を実行しませんalloca
が、強制するalloca
と、呼び出し元のコンテキストで発生します(つまり、呼び出し元が戻るまでスタックは解放されません)alloca
に、移植性のない機能/ハックから標準化された拡張機能に移行しましたが、いくつかの否定的な認識が続く可能性がありますmalloc
存続期間は関数スコープにバインドされます。関数スコープは、の明示的な制御よりもプログラマーに適している場合とそうでない場合があります。malloc
あると、割り当て解除について考えることが奨励されます-ラッパー関数(例WonderfulObject_DestructorFree(ptr)
)を介して管理されている場合、この関数は、クライアントに明示的な変更を加えることなく、実装のクリーンアップ操作(ファイル記述子のクローズ、内部ポインターの解放、ロギングの実行など)のポイントを提供しますコード:一貫して採用するのが良いモデルである場合があります
WonderfulObject* p = WonderfulObject_AllocConstructor();
は、「コンストラクター」がmalloc
メモリを返す関数である場合に可能です(関数が格納される値を返した後もメモリは割り当てられたままであるためp
)。 「コンストラクター」が使用する場合alloca
WonderfulObject_AllocConstructor
これを達成できますが、「マクロは悪」であり、マクロ以外のコードが互いに競合し、意図しない置換が発生し、その結果、診断が困難になる可能性があります。free
操作はValGrind、Purifyなどで検出できますが、欠落している「デストラクタ」呼び出しは常に検出できるとは限りません。一部のalloca()
実装(GCCなど)は、にインラインマクロを使用するためalloca()
、メモリ使用量診断ライブラリの実行時の置換は、//(電気柵など)の場合とはmalloc
異なりますrealloc
。free
多くのシステムでは、alloca()によって予約されたスタックスペースが関数引数用のスペースの中央のスタックに表示されるため、関数呼び出しの引数のリスト内でalloca()を使用することはできません。
この質問がCとタグ付けされていることは知っていますが、C ++プログラマーとして、C ++を使用してalloca
、以下のコード(およびここでは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 ];
ます。注意して合理的にすれば大丈夫です。
この「古い」質問に対する興味深い回答がたくさんあり、比較的新しい回答もいくつかありますが、これについて言及しているものは見つかりませんでした。
適切かつ注意深く使用すると、
alloca()
(おそらくアプリケーション全体で)小さな可変長の割り当て(または利用可能な場合はC99 VLA)を一貫して使用すると、固定長の特大のローカル配列を使用する同等の実装よりも全体的なスタックの増加を抑えることができます。したがって、慎重に使用すれば、スタックに適してalloca()
いる可能性があります。
私はその引用を見つけました....OK、私はその引用を作りました。しかし、本当に、それについて考えてください....
@j_random_hackerは、他の回答の下での彼のコメントで非常に正しいです:alloca()
特大のローカル配列を支持しての使用を避けても、プログラムがスタックオーバーフローから安全になるわけではありません(コンパイラが使用する関数のインライン化を可能にするのに十分古い場合を除きますalloca()
。アップグレードするか、alloca()
内部ループを使用しない限り、その場合は...alloca()
内部ループを使用しないでください)。
私はデスクトップ/サーバー環境と組み込みシステムに取り組んできました。多くの組み込みシステムは、ヒープをまったく使用しません(ヒープをサポートするためにリンクすることさえありません)。これには、動的に割り当てられたメモリが悪であるという認識が含まれるためです。一度に何年も再起動するか、動的メモリが危険であるというより合理的な理由は、アプリケーションが誤ったメモリ枯渇のポイントまでヒープを断片化しないことを確実に知ることができないためです。そのため、組み込みプログラマーにはいくつかの選択肢が残されています。
alloca()
(またはVLA)は、この作業に最適なツールである可能性があります。プログラマーがスタックに割り当てられたバッファーを「考えられるすべてのケースを処理するのに十分な大きさ」にするということを何度も目にしました。深くネストされたコールツリーでは、その(アンチ?)パターンを繰り返し使用すると、スタックの使用が誇張されます。(20レベルの深さのコールツリーを想像してみてください。各レベルで、さまざまな理由で、関数は1024バイトのバッファを「安全のために」盲目的に過剰に割り当てますが、通常は16バイト以下しか使用しません。まれなケースではもっと使用する場合があります。)別の方法は使用することですalloca()
またはVLAを使用して、スタックに不必要な負担をかけないように、関数が必要とするだけのスタックスペースを割り当てます。うまくいけば、コールツリー内の1つの関数が通常よりも大きい割り当てを必要とする場合、コールツリー内の他の関数は引き続き通常の小さな割り当てを使用し、アプリケーションスタック全体の使用量は、すべての関数がローカルバッファを盲目的に過剰に割り当てた場合よりも大幅に少なくなります。 。
alloca()
...このページの他の回答に基づくと、VLAは安全であるはずです(ループ内から呼び出された場合、スタック割り当てを複合しません)が、alloca()
を使用している場合は、ループ内で使用しないように注意して、別の関数のループ内で呼び出される可能性がある場合は、関数をインライン化できないようにしてください。
誰もこれについて言及していないと思います。コンパイラが関数のスタックフレームのサイズを知ることができないため、関数でallocaを使用すると、関数に適用できる最適化が妨げられたり無効になったりします。
たとえば、Cコンパイラによる一般的な最適化は、関数内でのフレームポインタの使用を排除することであり、代わりにスタックポインタを基準にしてフレームアクセスが行われます。したがって、一般的に使用するレジスタがもう1つあります。ただし、関数内でallocaが呼び出された場合、関数の一部でspとfpの違いが不明になるため、この最適化を行うことはできません。
その使用の希少性と標準関数としての日陰の状態を考えると、コンパイラの設計者は、allocaで動作させるために少し以上の努力が必要な場合、allocaで問題を引き起こす可能性のある最適化を無効にする可能性があります。
更新: 可変長のローカル配列がCに追加され、これらはallocaと非常によく似たコード生成の問題をコンパイラーに提示するため、「使用の希少性と日陰の状態」は基盤となるメカニズムには適用されないことがわかります。しかし、allocaまたはVLAのいずれかを使用すると、それらを使用する関数内のコード生成が危険にさらされる傾向があるのではないかと思います。コンパイラ設計者からのフィードバックを歓迎します。
スタックオーバーフローによる未定義動作の可能性については、すでに誰もが指摘していますが、Windows環境には、構造化例外(SEH)とガードページを使用してこれをキャッチする優れたメカニズムがあることを述べておきます。スタックは必要な場合にのみ増大するため、これらのガードページは未割り当ての領域に存在します。(スタックをオーバーフローさせることによって)それらに割り当てると、例外がスローされます。
このSEH例外をキャッチし、_resetstkoflwを呼び出してスタックをリセットし、陽気な方法で続行できます。それは理想的ではありませんが、少なくとも何かがファンに当たったときに何かがうまくいかなかったことを知るための別のメカニズムです。*nixには私が気付いていない似たようなものがあるかもしれません。
allocaをラップして内部で追跡することにより、最大割り当てサイズを制限することをお勧めします。あなたがそれについて本当にハードコアであるなら、あなたはあなたの関数の上部にいくつかのスコープ歩哨を投げて、関数スコープ内のアロカ割り当てを追跡し、プロジェクトに許可された最大量に対してこれを健全性チェックすることができます。
また、メモリリークを許可しないことに加えて、allocaはメモリの断片化を引き起こしません。これは非常に重要です。アロカをインテリジェントに使用すれば、アロカは悪い習慣ではないと思います。これは基本的にすべてに当てはまります。:-)
落とし穴の1つalloca
は、longjmp
巻き戻すことです。
つまり、コンテキストを保存しsetjmp
、次にalloca
メモリを保存してlongjmp
から、コンテキストに保存すると、メモリが失われる可能性がありalloca
ます。スタックポインタが元の場所に戻ったため、メモリは予約されなくなりました。関数を呼び出すか、別の関数を実行alloca
すると、元の関数が壊滅しますalloca
。
明確にするために、ここで私が具体的に言及しているのは、発生longjmp
した機能から戻らない状況alloca
です!むしろ、関数はsetjmp
;でコンテキストを保存します。次に、でメモリを割り当てalloca
、最後にそのコンテキストでlongjmpが実行されます。その関数のalloca
メモリはすべて解放されるわけではありません。以降に割り当てられたすべてのメモリsetjmp
。もちろん、私は観察された行動について話している。alloca
私が知っていることについては、そのような要件は文書化されていません。
ドキュメントの焦点は通常、メモリがブロックではなく関数alloca
のアクティブ化に関連付けられているという概念にあります。の複数の呼び出しは、関数が終了したときにすべて解放されるスタックメモリをさらに取得します。そうではありません。メモリは実際にはプロシージャコンテキストに関連付けられています。コンテキストがで復元されると、以前の状態も復元されます。これは、スタックポインタレジスタ自体が割り当てに使用され、(必然的に)に保存および復元された結果です。alloca
longjmp
alloca
jmp_buf
ちなみに、これがそのように機能する場合、で割り当てられたメモリを意図的に解放するためのもっともらしいメカニズムを提供しalloca
ます。
私はバグの根本的な原因としてこれに遭遇しました。
理由は次のとおりです。
char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;
誰もがこのコードを書くわけではありませんが、渡すサイズの引数は、ほぼ確実に、プログラムをそのような巨大なものalloca
にすることを悪意を持って狙う可能性のある、ある種の入力から来ています。alloca
結局のところ、サイズが入力に基づいていないか、大きくなる可能性がない場合は、なぜ小さな固定サイズのローカルバッファーを宣言しなかったのでしょうか。
事実上、C99 vlasを使用するすべてのコードalloca
には重大なバグがあり、クラッシュ(運が良ければ)または特権の侵害(運が悪ければ)につながります。
alloca()は素晴らしく効率的です...しかしそれはまた深く壊れています。
ほとんどの場合、ローカル変数とメジャーサイズを使用して置き換えることができます。大きなオブジェクトに使用する場合は、通常、ヒープに配置する方が安全です。
本当にCが必要な場合は、VLAを使用できます(C ++にはvlaがない、残念です)。スコープの動作と一貫性に関しては、alloca()よりもはるかに優れています。私が見ているように、 VLAは一種のalloca()が正しく作成されています。
もちろん、必要なスペースの大部分を使用するローカル構造または配列の方が優れています。そのような大部分のヒープ割り当てがない場合は、プレーンなmalloc()を使用するのがおそらく適切です。alloca()またはVLAのいずれかが本当に必要な正気のユースケースは見当たりません。
alloca()
カーネルよりも特に危険な場所malloc()
-一般的なオペレーティングシステムのカーネルには、ヘッダーの1つにハードコードされた固定サイズのスタックスペースがあります。アプリケーションのスタックほど柔軟ではありません。保証されていないサイズでを呼び出すとalloca()
、カーネルがクラッシュする可能性があります。特定のコンパイラはalloca()
、カーネルコードのコンパイル中にオンにする必要がある特定のオプションの下での使用(さらにはVLA)を警告します。ここでは、ハードコードされた制限によって固定されていないメモリをヒープに割り当てることをお勧めします。
プロセスでは、使用可能なスタックスペースの量が限られています。これは、で使用可能なメモリの量よりはるかに少ない量ですmalloc()
。
使用alloca()
することで、スタックオーバーフローエラーが発生する可能性が大幅に高まります(運が良ければ、そうでない場合は不可解なクラッシュが発生します)。
で割り当てられたブロックを超えて誤って書き込んだ場合alloca
(たとえば、バッファオーバーフローが原因で)、関数のリターンアドレスはスタックの「上」、つまり割り当てられたブロックの後にあるため、上書きされます。
この結果は2つあります。
プログラムは見事にクラッシュし、クラッシュした理由や場所を特定することはできません(フレームポインタが上書きされるため、スタックはランダムなアドレスにアンワインドする可能性があります)。
悪意のあるユーザーがスタックに配置される特別なペイロードを作成して実行される可能性があるため、バッファオーバーフローは何倍も危険になります。
対照的に、ヒープ上のブロックを超えて書き込むと、「ただ」ヒープが破損します。プログラムはおそらく予期せず終了しますが、スタックを適切に巻き戻し、悪意のあるコードが実行される可能性を減らします。
悲しいことに、本当に素晴らしいものalloca()
は、ほとんど素晴らしいtccから欠落しています。Gccにはありますalloca()
。
それはそれ自身の破壊の種をまきます。デストラクタとしてのリターン付き。
同様malloc()
に、失敗すると無効なポインタが返され、MMUを備えた最新のシステムでセグメンテーション違反が発生します(できれば、MMUを使用せずにシステムを再起動します)。
自動変数とは異なり、実行時にサイズを指定できます。
再帰でうまく機能します。静的変数を使用して末尾再帰に似たものを実現し、他のいくつかの変数を使用して各反復に情報を渡すことができます。
深く押しすぎると、セグメンテーション違反が発生することが保証されます(MMUがある場合)。
malloc()
システムのメモリが不足している場合はNULLを返すため(割り当てられている場合はセグメンテーション違反も発生します)、はこれ以上提供されないことに注意してください。つまり、あなたができることは保釈するか、それをなんらかの方法で割り当てようとすることだけです。
使用するmalloc()
には、グローバルを使用してNULLを割り当てます。ポインタがNULLでない場合は、を使用する前にポインタを解放しますmalloc()
。
realloc()
既存のデータをコピーしたい場合は、一般的なケースとして使用することもできます。の後にコピーまたは連結する場合は、解決する前にポインタを確認する必要がありますrealloc()
。
実際、allocaがスタックを使用することは保証されていません。実際、allocaのgcc-2.95実装は、malloc自体を使用してヒープからメモリを割り当てます。また、その実装にはバグがあり、gotoをさらに使用してブロック内で呼び出すと、メモリリークが発生し、予期しない動作が発生する可能性があります。決して使用してはいけないと言っているわけではありませんが、アロカは、それが元から離れるよりも多くのオーバーヘッドにつながることがあります。
私の意見では、alloca()は、利用可能な場合、制限された方法でのみ使用する必要があります。「goto」の使用と非常によく似ていますが、それ以外の点では合理的な人々のかなりの数が、alloca()の使用だけでなく、その存在にも強い嫌悪感を抱いています。
組み込み用途の場合、スタックサイズがわかっていて、割り当てのサイズに関する規則と分析によって制限を課すことができ、コンパイラをアップグレードしてC99 +をサポートできない場合は、alloca()を使用しても問題ありません。それを使用することが知られています。
利用可能な場合、VLAにはalloca()に比べていくつかの利点があります。コンパイラーは、配列スタイルのアクセスが使用されたときに範囲外のアクセスをキャッチするスタック制限チェックを生成できます(コンパイラーがこれを行うかどうかはわかりませんが、可能ですコードを分析することで、配列アクセス式が適切に制限されているかどうかを判断できます。自動車、医療機器、アビオニクスなどの一部のプログラミング環境では、この分析は、自動(スタック上)と静的割り当て(グローバルまたはローカル)の両方の固定サイズのアレイに対しても実行する必要があることに注意してください。
データとリターンアドレス/フレームポインタの両方をスタックに格納するアーキテクチャでは(私が知っていることから、それがすべてです)、変数のアドレスを取得できるため、スタックに割り当てられた変数は危険である可能性があり、チェックされていない入力値は許可する可能性がありますあらゆる種類のいたずら。
埋め込みスペースでは移植性はそれほど重要ではありませんが、慎重に制御された状況以外でalloca()を使用することには反対の良い議論です。
埋め込みスペースの外では、効率を上げるために主にロギングおよびフォーマット関数内でalloca()を使用しました。また、非再帰的な字句スキャナーでは、トークン化と分類中に一時構造(alloca()を使用して割り当てられたもの)が作成され、その後永続的になります。オブジェクト(malloc()を介して割り当てられる)は、関数が戻る前に入力されます。小さな一時構造にalloca()を使用すると、永続オブジェクトが割り当てられるときの断片化が大幅に減少します。
誰もこれについて言及していないと思いますが、allocaには、mallocに必ずしも存在しない深刻なセキュリティ問題もあります(これらの問題は、動的かどうかに関係なく、スタックベースの配列でも発生します)。メモリはスタックに割り当てられるため、バッファオーバーフロー/アンダーフローは、mallocだけの場合よりもはるかに深刻な結果をもたらします。
特に、関数のリターンアドレスはスタックに格納されます。この値が破損した場合、コードはメモリの任意の実行可能領域に移動する可能性があります。コンパイラは、これを困難にするために非常に長い時間を費やします(特に、アドレスレイアウトをランダム化することによって)。ただし、戻り値が破損している場合はSEGFAULTが最良のケースであるため、これは単なるスタックオーバーフローよりも明らかに悪いですが、ランダムなメモリの実行を開始したり、最悪の場合、プログラムのセキュリティを危険にさらすメモリの一部の領域を実行したりする可能性もあります。 。
GNUドキュメンテーションによって導入されたこの例に誰も言及しないのはなぜですか?
https://www.gnu.org/software/libc/manual/html_node/Advantages-of-Alloca.html
で実行された非ローカル出口
longjmp
(非ローカル出口alloca
を参照)は、を呼び出した関数を介して終了するときに、で割り当てられたスペースを自動的に解放しますalloca
。これが使用する最も重要な理由ですalloca
読み上げ順序を提案する1->2->3->1
:
アロカおよび可変長配列のIMOの最大のリスクは、割り当てサイズが予想外に大きい場合に非常に危険な方法で失敗する可能性があることです。
スタック上の割り当てには、通常、ユーザーコードのチェックインはありません。
最近のオペレーティングシステムでは、通常、スタックオーバーフローを検出するためにガードページが下に配置されます*。スタックがオーバーフローすると、カーネルはスタックを拡張するか、プロセスを強制終了する可能性があります。Linuxは、2017年にこのガード領域をページよりも大幅に大きくするように拡張しましたが、サイズはまだ有限です。
したがって、原則として、以前の割り当てを利用する前に、スタックに1ページ以上を割り当てることは避けるのが最善です。アロカ配列または可変長配列を使用すると、攻撃者がスタック上で任意のサイズを割り当てて、ガードページをスキップして任意のメモリにアクセスできるようになる可能性があります。
*今日のほとんどの普及したシステムでは、スタックは下向きに成長します。
_alloca()
ここでのほとんどの回答は、主に要点を見逃しています。単に大きなオブジェクトをスタックに格納するよりも、使用が潜在的に悪い理由があります。
自動ストレージとの主な違い_alloca()
は、後者には追加の(重大な)問題があることです。割り当てられたブロックはコンパイラによって制御されないため、コンパイラがそれを最適化またはリサイクルする方法はありません。
比較:
while (condition) {
char buffer[0x100]; // Chill.
/* ... */
}
と:
while (condition) {
char* buffer = _alloca(0x100); // Bad!
/* ... */
}
後者の問題は明らかなはずです。