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()
関数内で行われたため、スタックを爆破しました。私の弁護において(そして、私は問題を発見した人ではありませんでした;私はそれを修正できなかったとき、私は上級開発者の一人に行って泣かなければなりませんでした)、それはまっすぐなalloca
ではなく、ATL文字列の1つでした変換マクロ。
したがって、レッスンは-インライン化される可能性があると思われる関数でalloca
を使用しないでください。
古い質問ですが、可変長配列に置き換える必要があるとは誰も言及していませんでした。
char arr[size];
の代わりに
char *arr=alloca(size);
これは標準C99にあり、多くのコンパイラのコンパイラ拡張機能として存在していました。
alloca()は、標準のローカル変数を使用できない場合、実行時にサイズを決定する必要があるため非常に便利です。 alloca()から取得したポインターは、この関数が戻った後は絶対に使用されないことを絶対に保証します。
あなたはかなり安全であることができます
本当の危険性は、誰かが後でこれらの条件に違反する可能性から生じます。それを念頭に置いて、テキストをフォーマットする関数にバッファを渡すのに最適です:)
このニュースグループの投稿 で述べたように、alloca
の使用が困難で危険であると考えられる理由はいくつかあります。
alloca
をサポートしているわけではありません。alloca
の意図した動作を異なる方法で解釈するため、それをサポートするコンパイラ間でも移植性は保証されません。1つの問題は、広くサポートされているものの、標準ではないことです。他の条件が同じであれば、一般的なコンパイラ拡張機能ではなく、常に標準関数を使用します。
まだallocaの使用は推奨されていません、なぜですか?
私はそのようなコンセンサスを知覚しません。多くの強力なプロ。いくつかの短所:
while
やfor
ループなど)または複数のスコープで使用される場合、メモリは反復/スコープごとに蓄積され、関数が終了するまで解放されません。これは、制御構造のスコープで定義される通常の変数とは対照的です(たとえば、for {int i = 0; i < 2; ++i) { X }
はXで要求されたalloca
- edメモリを蓄積しますが、固定サイズの配列のメモリは反復ごとにリサイクルされます)。inline
を呼び出すalloca
関数を使用しませんが、それらを強制すると、呼び出し元のコンテキストでalloca
が発生します(つまり、呼び出し元が戻るまでスタックは解放されません)alloca
は移植性のない機能/ハックから標準化された拡張機能に移行しましたが、否定的な認識が続く場合がありますmalloc
の明示的な制御よりもプログラマに適している場合とそうでない場合がありますmalloc
を使用する必要があるため、割り当て解除について考えることが推奨されます-ラッパー関数(たとえばWonderfulObject_DestructorFree(ptr)
)で管理されている場合、関数は明示的な変更なしで実装クリーンアップ操作(ファイル記述子を閉じる、内部ポインターを解放する、ログを記録するなど)のポイントを提供しますクライアントコードへ:一貫して採用することが良いモデルである場合がありますWonderfulObject* p = WonderfulObject_AllocConstructor();
のようなものが必要です。これは、「コンストラクタ」がmalloc
edメモリを返す関数の場合に可能です(関数が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 ];
とまったく同じくらいオーバーフローを引き起こす可能性があります。慎重かつ合理的であり、あなたは大丈夫です。
スタックオーバーフローによる未定義の潜在的な動作である大きなことは既に誰もが指摘していますが、Windows環境には、構造化例外(SEH)とガードページを使用してこれをキャッチする優れたメカニズムがあることに言及する必要があります。スタックは必要に応じて大きくなるだけなので、これらのガードページは割り当てられていない領域に存在します。 (スタックをオーバーフローさせることにより)それらに割り当てた場合、例外がスローされます。
このSEH例外をキャッチし、_resetstkoflwを呼び出してスタックをリセットし、陽気な方法で続行できます。それは理想的ではありませんが、ファンに何かが当たったときに何かが間違っていることを少なくとも知るための別のメカニズムです。 * nixには、私が知らない似たようなものがあるかもしれません。
Allocaをラップして内部的に追跡することにより、最大割り当てサイズを制限することをお勧めします。本当にハードコアな場合は、関数の最上部にスコープセントリーを投げて、関数スコープ内のalloca割り当てを追跡し、これをプロジェクトで許可されている最大量に対して健全性チェックできます。
また、メモリリークを許可しないことに加えて、allocaは非常に重要なメモリの断片化を引き起こしません。 allocaを賢く使用するのが悪い習慣だとは思いません。これは基本的にすべてに当てはまります。 :-)
alloca()は素晴らしく効率的です...
ほとんどの場合、ローカル変数とメジャーサイズを使用して置き換えることができます。大きいオブジェクトに使用する場合、通常はヒープに置く方が安全です。
Cが本当に必要な場合は、VLAを使用できます(C++にはvlaはありませんが、あまりにも悪いです)。それらは、スコープの動作と一貫性に関してalloca()よりもはるかに優れています。私がそれを見るようにVLAは一種のalloca()を正しくしました。
もちろん、必要なスペースの大部分を使用するローカル構造または配列の方が優れています。また、このような大部分のヒープ割り当てがない場合は、通常のmalloc()を使用するのが適切です。 alloca()またはVLA。のいずれかが本当に必要な場合の正気な使用例はありません。
この「古い」質問に対する興味深い回答がたくさんあり、比較的新しい回答もありますが、これについて言及しているものは見つかりませんでした。
適切に注意して使用すると、
alloca()
(おそらくアプリケーション全体)を一貫して使用して小さな可変長割り当て(または利用可能な場合はC99 VLA)を処理すると、他の同等のものよりも全体的なスタックの増加につながる可能性があります固定長の特大のローカル配列を使用した実装。したがって、alloca()
は、スタックに適しています慎重に使用する場合.
その引用を見つけました.... OK、その引用を作成しました。しかし、実際に考えてみてください....
@j_random_hackerは、他の答えの下で彼のコメントに非常に正しいです:オーバーサイズのローカル配列を支持してalloca()
の使用を避けても、スタックオーバーフローからプログラムが安全になりません(コンパイラがalloca()
を使用する関数のインライン化を許可するほど古い場合を除く)アップグレードする必要があります。または、ループ内でalloca()
を使用しない場合は、ループ内でalloca()
を使用しないでください。
私はデスクトップ/サーバー環境と組み込みシステムに取り組んできました。多くの組み込みシステムは、ヒープをまったく使用しません(それらをサポートするためにリンクすることすらありません)。これは、動的に割り当てられたメモリが、アプリケーションのメモリリークのリスクにより悪であるという認識を含む理由からです。一度に何年もリブートするか、アプリケーションがヒープを誤ったメモリ枯渇の点まで断片化しないことが確実ではないため、動的メモリは危険であるというより合理的な正当化。そのため、組み込みプログラマには選択肢がほとんどありません。
alloca()
(またはVLA)は、ジョブに最適なツールかもしれません。私は、プログラマがスタックに割り当てられたバッファを「可能なケースを処理するのに十分な大きさ」にする時間を何度も見ました。深くネストされたコールツリーでは、その(反?)パターンを繰り返し使用すると、スタックが誇張されて使用されます。 (コールツリーが20レベルの深さであると想像してください。さまざまな理由で、各レベルで、通常は16個以下しか使用せずに、「安全のため」に1024バイトのバッファを盲目的に過剰割り当てします。代わりにalloca()
またはVLAを使用し、関数に必要なだけのスタックスペースのみを割り当てて、スタックに不必要な負担をかけないようにすることもできます。コールツリー内の1つの関数が通常よりも大きい割り当てを必要とし、コールツリー内の他の関数がまだ通常の小さな割り当てを使用し、アプリケーションスタック全体の使用量がすべての関数がローカルバッファを盲目的に過剰割り当てした場合よりもはるかに少ないことを願っています。
alloca()
の使用を選択した場合...このページの他の回答に基づいて、VLAは安全であるように思われます(ループ内から呼び出された場合、スタック割り当てを複合しません)が、alloca()
を使用している場合は、ループ内で使用しないように注意してくださいmakesure別の関数のループ内で呼び出される可能性がある場合、関数をインライン化できません。
alloca()
がmalloc()
よりも特に危険な場所はカーネルです。典型的なオペレーティングシステムのカーネルには、ヘッダーの1つにハードコードされた固定サイズのスタックスペースがあります。アプリケーションのスタックほど柔軟ではありません。保証されていないサイズでalloca()
を呼び出すと、カーネルがクラッシュする可能性があります。特定のコンパイラは、カーネルコードのコンパイル中にオンにする必要のある特定のオプションでalloca()
(さらにはVLAも)の使用を警告します-ここでは、ハードコードされた制限によって修正されないヒープにメモリを割り当てる方が良いです。
誤ってalloca
で割り当てられたブロックを超えて書き込んだ場合(たとえば、バッファオーバーフローが原因で)、関数のreturn addressが上書きされます。その1つはスタックの「上」にあります。つまり、after割り当てられたブロックです。
この結果は2つあります。
プログラムは見事にクラッシュし、クラッシュした理由や場所を特定することはできません(フレームポインターが上書きされるため、スタックがランダムアドレスに巻き戻される可能性が高くなります)。
悪意のあるユーザーが特別なペイロードを作成してスタックに配置し、実行される可能性があるため、バッファオーバーフローが何倍も危険になります。
対照的に、ヒープ上のブロックを超えて書き込むと、ヒープが破損するだけです。プログラムはおそらく予期せず終了しますが、スタックを適切に巻き戻すため、悪意のあるコードが実行される可能性が低くなります。
コンパイラーは関数のスタックフレームのサイズを知ることができないため、関数でallocaを使用すると、関数に適用される可能性のある最適化を妨げたり無効にしたりします。
たとえば、Cコンパイラによる一般的な最適化は、関数内でのフレームポインターの使用を排除することであり、代わりにスタックポインターを基準にしてフレームアクセスが行われます。そのため、一般的な使用のためのもう1つのレジスタがあります。ただし、関数内でallocaが呼び出された場合、spとfpの違いは関数の一部で不明になるため、この最適化は実行できません。
その使用の希少性、および標準関数としての日陰の状態を考えると、コンパイラ設計者はany最適化を無効にする可能性が非常に高いため、might allocaで動作させるための努力はほとんどありません。
alloca
の落とし穴の1つは、longjmp
が巻き戻すことです。
つまり、setjmp
、次にalloca
のメモリ、コンテキストへのlongjmp
でコンテキストを保存すると、alloca
のメモリが失われる可能性があります(なんらかの通知なしに)。スタックポインターは元の場所に戻っているため、メモリは予約されていません。関数を呼び出すか、別のalloca
を実行すると、元のalloca
が上書きされます。
明確にするために、ここで特に言及しているのは、longjmp
が発生した関数からalloca
が返されない状況です!むしろ、関数はsetjmp
でコンテキストを保存します。その後、alloca
でメモリを割り当て、最後にそのコンテキストに対してlongjmpが実行されます。その関数のalloca
メモリはすべて解放されません。 setjmp
以降に割り当てたすべてのメモリ。もちろん、私は観察された行動について話している。私が知っているalloca
のそのような要件は文書化されていません。
ドキュメントの焦点は通常、alloca
メモリがブロックではなくfunctionアクティベーションに関連付けられているという概念にあります。 alloca
の複数の呼び出しは、関数が終了したときにすべて解放されるスタックメモリを増やすだけです。そうではない;メモリは実際にプロシージャコンテキストに関連付けられています。コンテキストがlongjmp
で復元されると、以前のalloca
状態も復元されます。これは、スタックポインターレジスタ自体が割り当てに使用され、(必要に応じて)jmp_buf
に保存および復元されるためです。
ちなみに、このように機能する場合、これはalloca
で割り当てられたメモリを意図的に解放するためのもっともらしいメカニズムを提供します。
バグの根本原因としてこれに遭遇しました。
悲しいことに、本当にすごいalloca()
がほとんどすごいtccにありません。 Gccにはalloca()
があります。
それはそれ自身の破壊の種をsoきます。デストラクタとしてのリターン付き。
malloc()
と同様に、失敗時に無効なポインターを返します。これは、MMUを備えた最新のシステムでセグメンテーション違反になります(そうでない場合は再起動します)。
自動変数とは異なり、実行時にサイズを指定できます。
再帰でうまく機能します。静的変数を使用して末尾再帰に似たものを実現し、他のいくつかを使用して各反復に情報を渡すことができます。
深く押しすぎると、セグメンテーション違反が発生します(MMUがある場合)。
malloc()
は、システムがメモリ不足のときにNULL(割り当てられた場合はセグメンテーション違反も発生します)を返すため、これ以上提供しないことに注意してください。つまりあなたができることは保釈するか、ただそれを何らかの方法で割り当てることです。
malloc()
を使用するには、グローバルを使用してNULLを割り当てます。ポインターがNULLでない場合、malloc()
を使用する前にポインターを解放します。
既存のデータをコピーする場合は、realloc()
を一般的なケースとして使用することもできます。 realloc()
の後にコピーまたは連結する場合は、解決する前にポインターをチェックする必要があります。
実際、allocaはスタックの使用を保証されていません。実際、alccaのgcc-2.95実装は、malloc自体を使用してヒープからメモリを割り当てます。また、その実装にはバグがあり、gotoをさらに使用してブロック内で呼び出すと、メモリリークが発生し、予期しない動作が発生する可能性があります。決して使用してはいけないというわけではありませんが、場合によってはallocaがreleavesよりも多くのオーバーヘッドをもたらします。
あまりきれいではありませんが、パフォーマンスが本当に重要な場合は、スタック上のスペースを事前に割り当てることができます。
すでにメモリの最大サイズが必要であり、オーバーフローチェックを維持したい場合は、次のようなことを行うことができます。
void f()
{
char array_on_stack[ MAX_BYTES_TO_ALLOCATE ];
SomeType *p = (SomeType *)array;
(...)
}
プロセスで使用できるスタック領域は限られています-malloc()
で使用可能なメモリ量よりもはるかに少ないです。
alloca()
を使用することにより、スタックオーバーフローエラーが発生する可能性が劇的に増加します(幸運な場合、またはそうでない場合は不可解なクラッシュ)。
Alloca関数は素晴らしく、すべての否定論者は単にFUDを広めています。
void foo()
{
int x = 50000;
char array[x];
char *parray = (char *)alloca(x);
}
配列とparrayはまったく同じで、まったく同じリスクです。一方が他方より優れていると言うことは、技術的な選択ではなく、構文上の選択です。
スタック変数とヒープ変数の選択に関しては、スコープ内の有効期間を持つ変数にスタックオーバーヒープを使用する長時間実行プログラムには多くの利点があります。ヒープの断片化を回避し、未使用の(使用不可能な)ヒープスペースでプロセススペースを増やすことを回避できます。クリーンアップする必要はありません。プロセスのスタック割り当てを制御できます。
なぜこれが悪いのですか?
私見では、誰もがスタックサイズの制限を使い果たすことを恐れているため、allocaは悪い習慣と見なされます。
このスレッドと他のリンクを読むことで多くのことを学びました。
私は主にallocaを使用して、msvcおよびgccでプレーンCファイルを変更せずにコンパイルできるようにします。C89スタイル、#ifdef _MSC_VERなどはありません。
ありがとうございました !このスレッドは私にこのサイトにサインアップさせた:)
私の意見では、alloca()は利用可能な場合、制約された方法でのみ使用されるべきです。 「goto」の使用と非常によく似ていますが、かなり合理的な人々の多くはalloca()の使用だけでなく、その存在にも強い嫌悪感を持っています。
組み込みで使用する場合、スタックサイズが既知であり、割り当てのサイズに慣習と分析によって制限を課すことができ、コンパイラをC99 +をサポートするようにアップグレードできない場合、alloca()の使用は問題ありません。それを使用することが知られています。
使用可能な場合、VLAにはalloca()よりもいくつかの利点があります:コンパイラーは、配列スタイルのアクセスが使用されているときに範囲外のアクセスをキャッチするスタック制限チェックを生成できます(コンパイラーがこれを行うかどうかはわかりませんが、コードの分析により、配列アクセス式が適切にバインドされているかどうかを判断できます。自動車、医療機器、航空電子工学などの一部のプログラミング環境では、自動(スタック上)と静的割り当て(グローバルまたはローカル)の両方の固定サイズのアレイでもこの分析を行う必要があることに注意してください。
スタックにデータと戻りアドレス/フレームポインターの両方を格納するアーキテクチャ(私が知っていることから、それがすべてです)では、変数のアドレスを取得でき、未チェックの入力値が許可する可能性があるため、スタックに割り当てられた変数は危険な場合がありますあらゆる種類のいたずら。
埋め込みスペースでは移植性はあまり問題になりませんが、慎重に制御された状況以外でalloca()を使用することに対する良い議論です。
埋め込みスペースの外側では、効率のために主にロギングおよびフォーマット関数の内部でalloca()を使用しました。また、トークン化および分類中に一時的な構造(alloca()を使用して割り当てられた後、永続的なオブジェクト(malloc()を介して割り当てられた)は、関数が戻る前に読み込まれます。
ここでのほとんどの答えは、主にポイントを逃しています。_alloca()
を使用することが、単にスタックに大きなオブジェクトを格納するよりも潜在的に悪い理由があります。
自動ストレージと_alloca()
の主な違いは、後者が追加の(深刻な)問題を抱えていることです:割り当てられたブロックはコンパイラーによって制御されないなので、コンパイラーが最適化またはリサイクルする方法はありません。
比較:
while (condition) {
char buffer[0x100]; // Chill.
/* ... */
}
で:
while (condition) {
char* buffer = _alloca(0x100); // Bad!
/* ... */
}
後者の問題は明らかです。
誰もこれに言及したとは思いませんが、allocaにはmallocに必ずしも存在しない重大なセキュリティ問題もあります(ただし、これらの問題はスタックベースの配列(動的かどうかに関係なく)にも発生します)。メモリはスタックに割り当てられるため、バッファオーバーフロー/アンダーフローは、mallocだけではなく、はるかに深刻な結果になります。
特に、関数の戻りアドレスはスタックに格納されます。この値が破損した場合、コードはメモリの実行可能な領域に移動する可能性があります。コンパイラは、これを困難にするために(特にアドレスレイアウトをランダム化することにより)多大な労力を費やしています。ただし、戻り値が破損している場合は最善のケースはSEGFAULTであるため、これは単なるスタックオーバーフローよりも明らかに悪いですが、ランダムなメモリの実行を開始したり、最悪の場合はプログラムのセキュリティを損なうメモリの一部の領域を開始することもあります。