alloca()の使用が優れたプラクティスと見なされないのはなぜですか?


401

alloca()の場合のように、ヒープではなくスタックにメモリを割り当てますmalloc()。したがって、ルーチンから戻ると、メモリが解放されます。したがって、これは実際に、動的に割り当てられたメモリを解放するという私の問題を解決します。割り当てられたメモリの解放はmalloc()大きな頭痛の種であり、何らかの理由で失敗した場合は、あらゆる種類のメモリの問題につながります。

alloca()上記の機能にもかかわらず、使用が推奨されないのはなぜですか?


40
簡単なメモ。この関数はほとんどのコンパイラにありますが、ANSI-C標準では必須ではないため、移植性が制限される可能性があります。別のことは、あなたがしてはいけないことです!free()は、取得したポインタであり、関数を終了すると自動的に解放されます。
merkuro、2009年

9
また、alloca()を含む関数は、そのように宣言されてもインライン化されません。
Justicle 2009年

2
@Justicle、あなたはいくつかの説明を提供できますか?私はこの振る舞いの背後に何があるのか​​非常に興味があります
migajek

47
移植性、呼び出す必要がないことfree(これは明らかに利点です)、インライン化できないこと(明らかにヒープ割り当ては非常に重い)など、すべてのノイズを忘れてくださいalloca。避けるべき唯一の理由は、サイズが大きいためです。つまり、大量のスタックメモリを浪費することはお勧めできません。さらに、スタックオーバーフローが発生する可能性があります。これが事実である場合- malloca/ を使用することを検討してくださいfreea
valdo

5
のもう1つのメリットallocaは、スタックをヒープのように断片化できないことです。WCRUは、独自の一連の問題(時間的局所性なし、準最適なリソースなし)を使用してカスタムメモリプールに頼ることなく静的に分析できるため、ハードリアルタイムの無期限スタイルアプリケーション、または安全性が重要なアプリケーションにも役立ちます。使用する)。
Andreas

回答:


245

答えはmanページのすぐそこにあります(少なくともLinuxでは):

戻り値alloca()関数は、割り当てられた領域の先頭を指すポインタを返します。割り当てがスタックオーバーフローを引き起こす場合、プログラムの動作は未定義です。

それは決して使用されるべきではないということではありません。私が取り組んでいるOSSプロジェクトの1つはそれを広範囲に使用しており、あなたがそれを悪用していない限り(alloca「巨大な値を持っている」)、それは問題ありません。「数百バイト」のマークを過ぎたらmalloc、代わりに、友達と使う時です。それでも割り当ての失敗が発生する可能性がありますが、少なくともスタックを破壊するのではなく、失敗の兆候がいくつかあります。


35
それで、大きな配列を宣言しても問題がないということで、本当に問題はありませんか?
TED

88
@Sean:はい、スタックオーバーフローのリスクが与えられた理由ですが、その理由は少しばかげています。第一に(ヴァイバブが言うように)大きなローカル配列はまったく同じ問題を引き起こしますが、それほど悪質ではありません。また、再帰はスタックを爆破するのと同じくらい簡単です。申し訳ありませんが、manページに示されている理由が正当化されているという一般的な考えに対抗できることを期待しています。
j_random_hacker

49
私のポイントは、alloca()がコーシャと見なされる他のもの(ローカル配列または再帰関数)とまったく同じように「悪い」ので、manページで与えられた正当化は意味をなさないということです。
j_random_hacker

39
@ninjalj:経験豊富なC / C ++プログラマーでalloca()はないが、ローカル配列や再帰を恐れる多くの人々はそうではないと思います(実際alloca()、「エレガントに見える」ため、大声で叫ぶ多くの人々は再帰を称賛します)。 。私はショーンのアドバイスに同意します(「alloca()は小さな割り当てには問題ありません」)が、alloca()を3つの中で唯一の邪悪なものとしてフレーム化するという考え方には同意しません。
j_random_hacker 2010

35
注:Linuxの「楽観的」なメモリ割り当て戦略を考えると、ヒープ枯渇の失敗を示すことはほとんどありません ...代わりに、malloc()は素敵な非NULLポインタを返し、実際にそれが指しているアドレス空間にアクセスすると、あなたのプロセス(または予測できない他のプロセス)がOOM-killerによってkillされます。もちろん、これはC / C ++の問題自体ではなく、Linuxの「機能」ですが、alloca()とmalloc()のどちらが「安全」かを検討する際には、注意が必要です。:)
ジェレミー・フリースナー2013

209

私が持っていた最も印象的なバグの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インライン化される可能性があると思われる関数では使用しないでください。


91
面白い。しかし、それはコンパイラのバグと見なされませんか?結局のところ、インライン化によってコードの動作が変更されました(allocaを使用して割り当てられたスペースの解放が遅れました)。
sleske

60
どうやら、少なくともGCCはこれを考慮に入れます:「関数定義での特定の使用法は、インライン置換に不適切になる可能性があることに注意してください。これらの使用法には、可変引数の使用、allocaの使用、[...]」があります。gcc.gnu.org/onlinedocs/gcc/Inline.html
sleske

137
どんなコンパイラーを吸っていましたか?
Thomas Eding、2011年

22
私が理解していないのは、コンパイラがスコープを有効に利用せずに、サブスコープ内のアロカが多かれ少なかれ「解放」されていると判断する理由です。関数から戻る(そうではないでしょうか?)
moala 2012年

7
私は反対票を投じましたが、答えはよく書かれています:明らかにコンパイラーのバグであるという理由で、あなたがallocaを失敗させている他の人に同意します。コンパイラーは、行うべきではない最適化で誤った仮定をしました。コンパイラーのバグを回避することは問題ありませんが、コンパイラー以外の問題はありません。
エヴァンキャロル

75

古い質問ですが、可変長配列に置き換える必要があると誰も述べていません。

char arr[size];

の代わりに

char *arr=alloca(size);

これは標準のC99に含まれており、多くのコンパイラーにコンパイラー拡張として存在していました。


5
アーサー・ウルフェルトの回答に対するコメントでジョナサン・レフラーが言及している。
ninjalj 2010

2
確かに、しかし、投稿する前にすべての回答を読んだにもかかわらず私はそれを見ていなかったので、それはどれほど簡単に見逃されているかも示しています。
PatrickSchlüter、2010

6
1つの注意-それらは可変長配列であり、動的配列ではありません。後者はサイズ変更可能で、通常はヒープに実装されます。
TimČas12年

1
一部のC ++をコンパイルするVisual Studio 2015にも同じ問題があります。
ahcox

2
Linus TorvaldsはLinuxカーネルのVLAを好みません。バージョン4.20の時点で、LinuxはほぼVLAフリーであるはずです。
クリスティアンCiupitu

60

alloca()は、実行時にサイズを決定する必要があるため、標準のローカル変数を使用できない場合に非常に役立ちます。また、alloca()から取得したポインターは、この関数が戻った後絶対に使用されないことを確実に保証でき ます

あなたはあなたがかなり安全であることができます

  • ポインタまたはそれを含むものは返さないでください。
  • ヒープに割り当てられた構造にポインタを格納しないでください
  • 他のスレッドにポインターを使用させない

本当の危険は、後で誰かがこれらの条件に違反する可能性から起こります。それを念頭に置いて、テキストをフォーマットする関数にバッファを渡すのは素晴らしいことです:)


12
C99のVLA(可変長配列)機能は、alloca()の使用を明示的に要求することなく、動的にサイズ設定されるローカル変数をサポートします。
ジョナサンレフラー、

2
ニート!詳細については、programmersheaven.com
Pointers

1
しかし、それはローカル変数へのポインタでの処理と違いはありません。彼らも同様に
浮気

2
@Jonathan Leffler allocaでできることの1つは、VLAでできないことですが、restrictキーワードを使用します。このように:float *制限heavily_used_arr = alloca(sizeof(float)* size); floatの代わりにheavily_used_arr [size]。サイズがコンパイル定数であっても、一部のコンパイラ(私の場合はgcc 4.8)がアセンブリを最適化するのに役立ちます。それについての私の質問を見てください:stackoverflow.com/questions/19026643/using-restrict-with-arrays
Piotr Lopusiewicz

@JonathanLeffler VLAは、それを含むブロックに対してローカルです。一方、alloca()関数の最後まで続くメモリを割り当てます。これは、VLAへの簡単で便利な変換がないように見えることを意味しf() { char *p; if (c) { /* compute x */ p = alloca(x); } else { p = 0; } /* use p */ }ます。のalloca使用をVLAの使用に自動的に変換することは可能であるが、方法を説明するためのコメント以上のものが必要であると思われる場合は、これを質問にできます。
Pascal Cuoq 2014年

40

このニュースグループの投稿で述べたように、使用allocaが困難で危険であると見なされる理由はいくつかあります。

  • すべてのコンパイラがサポートしてallocaいるわけではありません。
  • 一部のコンパイラは意図した動作をalloca異なる方法で解釈するため、それをサポートするコンパイラ間でも移植性は保証されません。
  • 一部の実装にはバグがあります。

24
このページのどこにもないリンクで言及したのはalloca()、スタックポインタとフレームポインタを保持するために別のレジスタが必要なことです。x86 CPU> = 386では、スタックポインタESPは両方に使用でき、解放されますEBP- alloca()が使用されない限り。
j_random_hacker

10
そのページのもう1つの良い点は、コンパイラのコードジェネレータが特別なケースとして処理しない限り、少なくとも1つの引数がf(42, alloca(10), 43);スタックポインタにプッシュされたalloca() 後にスタックポインタが調整される可能性が原因でクラッシュする可能性があることです。
j_random_hacker

3
リンクされた投稿はジョン・レバインによって書かれたようです-「リンカーとローダー」を書いた男、私は彼の言うことをすべて信頼します。
user318904 2011

3
リンクされた投稿は、John Levineによる投稿への返信です。
A. Wilcox 2014

6
心に留めておいてください。1991年以降、多くの変更がありました。すべての最新のCコンパイラー(2009年でも)は、allocaを特殊なケースとして処理する必要があります。これは通常の関数ではなく組み込み関数であり、関数を呼び出すことさえできません。したがって、パラメーターの割り当て問題(1970年代からK&R Cで発生しました)は今のところ問題にはなりません。トニーDの答えについて私が行ったコメントの詳細
greggo

26

1つの問題は、広くサポートされているものの、標準ではないことです。他の条件が同じであれば、一般的なコンパイラー拡張ではなく、常に標準関数を使用します。


21

それでもallocaの使用は推奨されません、なぜですか?

私はそのようなコンセンサスを認識していません。強力なプロがたくさん。いくつかの短所:

  • C99は可変長配列を提供します。これは、表記が固定長配列とより一貫しており、全体的に直観的であるため、優先的に使用されることがよくあります。
  • 多くのシステムでは、ヒープで使用できるよりもスタックで使用できる全体のメモリ/アドレス空間が少ないため、プログラムが(スタックオーバーフローによる)メモリ消耗の影響をわずかに受けやすくなります。スタックがヒープのように自動的に成長しない理由の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-edメモリを返す関数である場合に可能です(関数がに格納される値を返した後もメモリは割り当てられたままなのでp)。 「コンストラクタ」が使用する場合alloca
      • のマクロバージョンでWonderfulObject_AllocConstructorはこれを実現できますが、「マクロは悪」であり、マクロと非マクロコードが競合して意図しない置換が発生し、結果として診断が困難になる可能性があります。
    • 欠落しているfree操作はValGrind、Purifyなどで検出できますが、欠落している「デストラクタ」呼び出しを常に検出できるとは限りません。一部のalloca()実装(GCCなど)はにインラインマクロを使用しているalloca()ため、メモリ使用量診断ライブラリのランタイム置換は、malloc/ realloc/のように実行できませんfree(例:電気柵)
  • 一部の実装には微妙な問題があります。たとえば、Linuxマンページから:

    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
    }
}

理由はいくつかの種類:-(取り扱いの魚特有の道の無い1
einpoklumを

@einpoklum:それは非常に啓発的です...ありがとう。
Tony Delroy、2015

1
言い換えましょう。これは非常に良い答えです。あなたが人々が一種のカウンターパターンを使うことを提案していると思うところまで。
einpoklum 2015

linuxのマンページからのコメントは非常に古く、確かに時代遅れです。最近のコンパイラーはすべてalloca()が何であるかを知っており、そのような靴ひもをつまずくことはありません。古いK&R Cでは、(1)すべての関数がフレームポインターを使用していました(2)すべての関数呼び出しは{push args on stack} {call func} {add#n、sp}でした。allocaはスタックを増やすだけのlib関数であり、コンパイラーはそのことについてさえ知りませんでした。(1)と(2)はもはや真実ではないので、allocaはそのようには機能しません(現在は組み込みです)。古いCでは、引数をプッシュする途中でallocaを呼び出すと、これらの仮定も明らかに破られます。
greggo 2017年

4
例については、私は一般的に何かを心配するだろうが必要です ....回避のメモリの破損にalways_inline
greggo

14

他のすべての答えは正しいです。ただし、使用して割り当てたいものalloca()がかなり小さいmalloc()場合は、使用するよりも速くて便利なテクニックだと思います。

つまり、alloca( 0x00ffffff )危険であり、オーバーフローを引き起こす可能性がありchar hugeArray[ 0x00ffffff ];ます。慎重かつ合理的であり、あなたは大丈夫です。


12

この「古い」質問に対する興味深い回答がたくさんあり、比較的新しい回答もいくつかありますが、これについて言及しているものは見つかりませんでした。

適切に注意して使用すると、alloca() (おそらくアプリケーション全体で)小さな可変長割り当て(またはC99 VLAが利用可能な場合)を処理するために一貫して使用すると、固定長の特大ローカル配列を使用する同等の実装よりも全体的なスタックの成長低下する可能性があります。だから、alloca()かもしれあなたのスタックのための良いあなたは慎重にそれを使用する場合。

私はその引用を見つけました... OK、私はその引用を作りました。しかし、本当に、それについて考えてください...

@j_random_hackerは非常に右の他の回答の下で彼のコメントである:の使用回避alloca()コンパイラが関数のインライン化を可能にするために、古い十分でない限り、特大のローカル配列の賛成では(スタックオーバーフローからあなたのプログラムが安全なことはありません使用することalloca()で、その場合、あなたがしなければなりませんアップグレードするか、alloca()ループ内で使用しない限り、ループ内で使用しないでくださいalloca())。

デスクトップ/サーバー環境と組み込みシステムに取り組んできました。多くの組み込みシステムはヒープをまったく使用しません(それらをサポートするためにリンクすることもありません)。これは、動的に割り当てられたメモリは、決してアプリケーションのメモリリークのリスクにより悪であるという認識を含むためです。一度に何年も再起動することもあれば、動的メモリが危険であるという正当な理由がある場合もあります。これは、アプリケーションがヒープを断片化して、誤ってメモリを使い尽くすことはないためです。そのため、組み込みプログラマーにはいくつかの選択肢があります。

alloca() (またはVLA)は、ジョブに最適なツールです。

プログラマーがスタックに割り当てられたバッファーを「可能なあらゆるケースを処理するのに十分な大きさ」にするところを何度も何度も見ました。深くネストされた呼び出しツリーでは、その(アンチ?)パターンを繰り返し使用すると、スタックの使用が誇張されます。(20レベルの呼び出しツリーを想像してください。さまざまな理由で各レベルで、関数は通常1024バイトのバッファーを「安全のために」盲目的に過剰に割り当てますが、通常は16バイト以下しか使用せず、非常にまれにそれ以上使用する場合があります。)alloca()またはVLAを使用して、関数が必要とするだけのスタックスペースのみを割り当て、スタックへの不必要な負荷を回避します。うまくいけば、呼び出しツリー内の1つの関数が通常よりも大きい割り当てを必要とする場合でも、呼び出しツリー内の他の関数は通常の小さい割り当てを使用しており、アプリケーションスタック全体の使用量は、すべての関数がローカルバッファを盲目的に割り当てすぎた場合よりも大幅に少なくなります。

しかし、使用することを選択した場合alloca()...

このページの他の回答に基づいて、VLAは安全であるようです(ループ内から呼び出された場合はスタック割り当てを複合しません)。ただし、alloca()を使用している場合は、ループ内で使用しないように注意し、別の関数のループ内で呼び出される可能性がある場合は、関数をインライン化できないようにしてください。


私はこの点に同意します。危険なのalloca()は本当ですが、メモリリークmalloc()についても同じことが言えます(なぜGCを使わないのですか?alloca()注意して使用すると、スタックサイズを減らすのに非常に役立ちます。
フェリペトネロ2017

特に組み込みで動的メモリを使用しないもう1つの理由は、スタックに固執するよりも複雑です。動的メモリを使用するには特別な手順とデータ構造が必要ですが、スタックでは(物事を単純化するために)スタックポインタからより大きな数を加算/減算することが問題です。
tehftw 2018

補足:「固定バッファの使用[MAX_SIZE]」の例は、オーバーコミットメモリポリシーがうまく機能する理由を示しています。プログラムは、バッファ長の制限を除いて、触れることができないメモリを割り当てます。したがって、Linux(および他のOS)が実際にメモリのページを割り当てるのは、最初に使用されるまで(malloc'dとは対照的に)は問題ありません。バッファが1ページよりも大きい場合、プログラムは最初のページのみを使用し、残りの物理メモリを浪費することはありません。
Katastic Voyage

@KatasticVoyage MAX_SIZEがシステムの仮想メモリページサイズのサイズより大きい(または少なくとも等しい)場合を除き、引数は水を保持しません。また、仮想メモリのない組み込みシステム(多くの組み込みMCUにはMMUがない)では、「プログラムがすべての状況で確実に実行されるようにする」という観点からは、オーバーコミットメモリポリシーが適切な場合がありますが、その保証にはスタックサイズの代償が伴います。同様に、そのオーバーコミットメモリポリシーをサポートするために割り当てる必要があります。一部の組み込みシステムでは、これは低価格製品の一部のメーカーが支払いたくない価格です。
phonetagger

11

誰もがスタックオーバーフローからの未定義の動作の可能性があるという大きなことをすでに指摘していますが、Windows環境には構造化例外(SEH)とガードページを使用してこれをキャッチする優れたメカニズムがあることを述べておきます。スタックは必要に応じて大きくなるだけなので、これらのガードページは割り当てられていない領域に存在します。それらに(スタックをオーバーフローすることによって)割り当てると、例外がスローされます。

このSEH例外をキャッチし、_resetstkoflwを呼び出してスタックをリセットし、楽しい方法で続行できます。これは理想的ではありませんが、少なくとも何かがファンに当たったときに何かがうまくいかなかったことを知るもう1つのメカニズムです。* nixは私が知らない類似のものを持っているかもしれません。

allocaをラップして内部的に追跡することで、最大割り当てサイズに上限を設けることをお勧めします。本当にハードコアである場合は、いくつかのスコープセントリーを関数の上部にスローして、関数スコープの割り当て割り当てを追跡し、プロジェクトに許可されている最大量に対してこれをチェックすることができます。

また、メモリリークを許可しないことに加えて、アロカはかなり重要なメモリの断片化を引き起こしません。allocaをインテリジェントに使用することは悪い習慣ではないと思います。これは基本的にすべてに当てはまります。:-)


問題は、それalloca()が非常に多くのスペースを要求する可能性があり、スタックポインタがヒープに配置されることです。これにより、サイズを制御できる攻撃者とalloca()そのバッファーに入るデータがヒープを上書きする可能性があります(これは非常に悪いことです)。
12431234123412341234123 2018年

SEHはWindows専用のものです。Windowsで実行しているコードのみに関心がある場合は素晴らしいことですが、コードをクロスプラットフォームにする必要がある場合(または、Windows以外のプラットフォームでのみ実行するコードを作成している場合)、依存することはできません。 SEH。
ジョージ

10

alloca()は素晴らしくて効率的です...しかし、それは深く壊れています。

  • 壊れたスコープの動作(ブロックスコープではなく関数スコープ)
  • mallocとの一貫性のない使用(alloca()- tedポインターは解放されるべきではないため、今後は、malloc()で取得したポインターのみをfree()に向けてポインターがどこから来ているかを追跡する必要があります)
  • インライン化も使用する場合の動作が悪い(呼び出し先がインライン化されているかどうかに応じて、スコープが呼び出し元関数に移動する場合がある)。
  • スタック境界チェックなし
  • 失敗した場合の未定義の動作(mallocのようにNULLを返さない...とにかくスタック境界をチェックしないため、失敗の意味は...)
  • ANSI標準ではない

ほとんどの場合、ローカル変数とメジャーサイズを使用して置き換えることができます。大きなオブジェクトに使用する場合は、ヒープ上に置くのが通常安全です。

本当にCが必要な場合は、VLAを使用できます(C ++にはvlaはありません。これらは、スコープの動作と一貫性に関してalloca()よりもはるかに優れています。私が見ているように、VLAは一種のalloca()が正しく作成されたものです。

もちろん、必要な領域の主要な部分を使用するローカル構造または配列の方が優れています。このような主要なヒープがない場合は、プレーンなmalloc()を使用したヒープの割り当てはおそらく正気です。alloca()VLAのどちらかが本当に必要な正気な使用例はありません


投票の理由がわかりません(ところで、コメントはありません)
gd1 '25

名前のみにスコープがあります。alloca名前は作成せず、存続期間を持つメモリ範囲のみを作成します。
curiousguy 2015年

@curiousguy:あなたは単に言葉で遊んでいるだけです。自動変数については、名前のスコープと一致するため、基礎となるメモリの寿命についても言えます。とにかく問題は私たちがそれをどのように呼ぶかではなく、allocaによって返されるメモリの寿命/スコープの不安定性と例外的な動作です。
クリス、2015年

2
allocaに対応する「freea」があり、「freea」を呼び出すとオブジェクトとそれ以降のすべてのオブジェクトを作成した「alloca」の効果が取り消されるという仕様と、機能内で「割り当てられた」ストレージがその中も「解放」される。これにより、ほぼすべての実装でalloca / freeaを互換性のある方法でサポートできるようになり、インライン化の問題が緩和され、一般的にははるかにクリーンになります。
スーパーキャット2016年

2
@supercat —私もそうしたいです。そのため(そしてそれ以上)、抽象化レイヤー(主にマクロとインライン関数)を使用するので、または直接呼び出しallocaたりしmallocないfreeようにします。私は物事が好きと言う{stack|heap}_alloc_{bytes,items,struct,varstruct}{stack|heap}_dealloc。したがって、heap_dealloc単に呼び出しfreestack_dealloc、何もしません。このようにして、スタックの割り当てはヒープの割り当てに簡単にダウングレードでき、意図もより明確になります。
トッドリーマン

9

理由は次のとおりです。

char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;

誰もがこのコードを書くわけではありませんが、渡されるサイズの引数は、allocaほぼ間違いなく、ある種の入力からのものであり、悪意を持ってプログラムをそのallocaような巨大なものにすることを目的としています。結局のところ、サイズが入力に基づいていない場合、または大きくなる可能性がない場合、なぜ、小さな固定サイズのローカルバッファーを宣言しないのですか?

事実上すべてのコードallocaまたはC99 vlas を使用するコードには重大なバグがあり、クラッシュ(幸運な場合)または特権の侵害(幸運でない場合)につながります。


1
世界は決して知らないかもしれません。:(とはいえ、私が持っている質問を明確にしていただければ幸いですalloca。それを使用するほとんどすべてのコードにバグがあるとおっしゃっていましたが、私はそれを使用するつもりでした。通常、このような主張は無視しますが、あなたからは私はしません。私は仮想マシンを書いていて、非常に高速化されているため、動的にではなく、スタック上の関数からエスケープしない変数を割り当てたいと思います。同じパフォーマンス特性を持つアプローチ?私はメモリプールに近づくことができることを知っていますが、それはまだそれほど安くありません。どうしますか?
GManNickG '26

7
何が危険かを知っていますか?これ:*0 = 9;すばらしい!!! 私はポインタを使用しないでください(または少なくともそれらを逆参照する必要があります)。エラー、待って。nullかどうかをテストできます。うーん。を介して割り当てるメモリのサイズをテストすることもできると思いallocaます。変な男。変だ。
Thomas Eding、

7
*0=9;は無効です。C。渡すサイズをallocaテストする場合、何に対してテストしますか?制限を知る方法はありません。既知の安全な小さな固定サイズ(8kなど)に対してテストする場合は、スタックで固定サイズの配列を使用することもできます。
R .. GitHub ICE HELPING ICEを停止

7
「サイズが十分に小さいことがわかっているか、入力に依存しているため、任意に大きくなる可能性がある」という問題は、再帰にも同じように強く当てはまることです。(どちらの場合も)実際の妥協点は、サイズに制限がある場合、small_constant * log(user_input)おそらく十分なメモリがあると想定することです。
j_random_hacker 2012

1
確かに、VLA / allocaが役立つケースを1つ特定しました。コールフレームで必要な最大スペースはNと同じ大きさで、すべての再帰レベルで必要なスペースの合計がNまたは関数である再帰アルゴリズム急速に成長しないNの。
R .. GitHub ICE HELPING ICE STOP 2012

9

コンパイラが関数のスタックフレームのサイズを認識できないため、関数でallocaを使用すると、関数に適用できる最適化が妨げられるか、無効になります。

たとえば、Cコンパイラによる一般的な最適化は、関数内でのフレームポインタの使用を排除することです。代わりに、フレームアクセスはスタックポインタに対して行われます。そのため、一般的に使用するためのレジスタがもう1つあります。ただし、関数内でallocaが呼び出された場合、spとfpの違いは関数の一部では不明であるため、この最適化は実行できません。

その使用の希少性、および標準機能としてその木陰状況を考えると、コンパイラ設計者は恐らく無効どんなことを最適かもしれないのはallocaと原因のトラブルは、場合は、それはallocaをして動作させるために少しの努力よりも多くかかるだろうし。

更新: 可変長ローカル配列がCに追加されており、これらがallocaとしてコンパイラーに非常によく似たコード生成の問題を提示しているため、「使用の珍しさと疑わしいステータス」は基礎となるメカニズムには適用されません。しかし、allocaまたはVLAのいずれかを使用すると、それらを使用する関数内のコード生成が損なわれる傾向があると私はまだ疑います。コンパイラ設計者からのフィードバックをお待ちしています。


1
可変長配列がC ++に追加されることはありませんでした。
Nir Friedman

@NirFriedman確かに。古い提案に基づいたウィキペディアの機能リストがあったと思います。
greggo

> allocaまたはVLAのいずれかを使用するとコード生成が損なわれる傾向があると私はまだ疑います。スタックポインターはコンパイル時に明らかではない方法で移動するため、allocaの使用にはフレームポインターが必要だと思います。allocaをループで呼び出して、より多くのスタックメモリを取得し続けるか、実行時に計算されたサイズなどを使用して呼び出すことができます。フレームポインターがある場合、生成されたコードはローカルへの安定した参照を持ち、スタックポインターは必要に応じて何でもできます。使用されていません。
カズ

8

一つの落とし穴allocaはそれをlongjmp巻き戻すことです。

つまり、を使用してコンテキストを保存するとsetjmpalloca一部のメモリが保存され、そのコンテキストに保存すると、メモリlongjmpが失われる可能性がありallocaます。スタックポインタが元の位置に戻ったため、メモリは予約されなくなりました。関数を呼び出すか別の関数を実行すると、元の関数allocaが上書きされますalloca

明確にするために、ここで具体的に言及しているのは、発生longjmpした関数から復帰しない状況allocaです!むしろ、関数はでコンテキストを保存しますsetjmp。次にメモリを割り当てalloca、最後にそのコンテキストに対してlongjmpを実行します。その関数のallocaメモリがすべて解放されるわけではありません。それ以降に割り当てられたすべてのメモリsetjmp。もちろん、私は観察された行動について話しています。そのような要件はalloca、私が知っていることについて文書化されていません。

ドキュメンテーションの焦点は通常、allocaメモリはブロックではなく機能のアクティブ化に関連付けられているという概念にあります。alloca関数の終了時にすべて解放されるスタックメモリを取得するだけの複数の呼び出し。そうではありません。メモリは実際にはプロシージャコンテキストに関連付けられています。でコンテキストが復元されるとlongjmp、以前のalloca状態も復元されます。これは、割り当てに使用されるスタックポインタレジスタ自体の結果であり、(必要に応じて)に保存および復元されますjmp_buf

ちなみに、これは、そのように機能する場合、で割り当てられたメモリを意図的に解放するためのもっともらしいメカニズムを提供しallocaます。

バグの根本原因としてこれに遭遇しました。


1
それはそれがやるべきことです- longjmp戻ってそれを行うので、プログラムはスタックで起こったすべてのことを忘れます:すべての変数、関数呼び出しなどallocaであり、スタック上の配列のようですので、それらは破壊されますスタック上の他のすべてのように。
tehftw 2018

1
man alloca「alloca()によって割り当てられたスペースはスタックフレーム内に割り当てられるため、関数の戻り値がlongjmp(3)またはsiglongjmp(3)の呼び出しによってジャンプされた場合、そのスペースは自動的に解放されます。」そのため、で割り当てられたメモリallocaがの後に破棄されることが文書化されていますlongjmp
tehftw 2018

@tehftw説明されている状況は、関数の戻りがによって飛び越されることなく発生しlongjmpます。ターゲット関数はまだ返っていません。それは行っているsetjmpalloca当時とlongjmplongjmp巻き戻しがありalloca、それはにあったものに状態のバックsetjmp時間を。つまり、移動されたポインタには、allocaマークされていないローカル変数と同じ問題がありますvolatile
Kaz

3
どうして予想外と思われるのかわかりません。あなたがするとsetjmp、その後alloca、その後longjmp、それは通常だと alloca巻き戻されることになります。のポイントはlongjmpsetjmp!で保存された状態に戻ることです。
tehftw 2018

@tehftwこの特定の相互作用が文書化されているのを見たことがありません。したがって、コンパイラーを使用した経験的な調査以外の方法では、信頼できるものではありません。
Kaz

7

カーネルalloca()よりも特に危険な場所malloc()-一般的なオペレーティングシステムのカーネルには、固定サイズのスタックスペースがヘッダーの1つにハードコーディングされています。アプリケーションのスタックほど柔軟ではありません。alloca()保証されていないサイズでを呼び出すと、カーネルがクラッシュする可能性があります。特定のコンパイラはalloca()、カーネルコードのコンパイル中にオンにする必要がある特定のオプションの下で(さらにはVLAについても)の使用を警告します。ここでは、ハードコードされた制限によって修正されないメモリをヒープに割り当てる方が適切です。


7
alloca()int foo[bar];どこbarかに任意の整数があるのと同じくらい危険です。
トッドリーマン

@ToddLehman正解です。その正確な理由により、カーネルでのVLAは数年間禁止されており、2018年以降VLAフリーになっています:-)
Chris Down

6

で割り当てられたブロックを超えて誤って書き込んだ場合alloca(たとえば、バッファーオーバーフローが原因)、関数の戻りアドレスはスタックの "上"にあるため、つまり、割り当てられたブロックの後にあるため、上書きします。

スタック上の_allocaブロック

この結果は2つあります。

  1. プログラムは見事にクラッシュし、クラッシュした理由や場所を特定することはできません(スタックは、上書きされたフレームポインターが原因で、ランダムなアドレスに巻き戻される可能性が最も高くなります)。

  2. 悪意のあるユーザーがスタックに入れられる特別なペイロードを作成し、実行される可能性があるため、バッファオーバーフローが何倍も危険になります。

対照的に、ヒープのブロックを超えて書き込むと、ヒープが破損するだけです。プログラムはおそらく予期せず終了しますが、スタックを適切に巻き戻し、悪意のあるコードが実行される可能性を減らします。


11
この状況では、固定サイズのスタックに割り当てられたバッファがバッファオーバーフローする危険性と劇的な違いはありません。この危険はに特有のものではありませんalloca
Phonetagger 2016年

2
もちろん違います。ただし、元の質問を確認してください。問題はallocamalloc(したがって、スタック上の固定サイズのバッファーではない)と比べて何が危険かということです。
rustyx

マイナーポイントですが、一部のシステム(PIC 16ビットマイクロプロセッサなど)のスタックは増加しています。
EBlake

5

悲しいことに、本当にalloca()素晴らしいtccには本当に素晴らしいものがありません。Gccは持っていますalloca()

  1. それは自身の破壊の種を蒔きます。デストラクタとして復帰します。

  2. malloc()それが失敗時に無効なポインターを返すように、MMUを備えた最新のシステムでsegfaultします(うまくいけば、それを使わずに再起動します)。

  3. 自動変数とは異なり、実行時にサイズを指定できます。

再帰でうまく動作します。静的変数を使用して末尾再帰に似たものを実現し、他のいくつかを使用して各反復に情報を渡すことができます。

深く押しすぎると、segfaultが確実になります(MMUがある場合)。

malloc()システムのメモリが不足している場合、NULLが返されるため(割り当てられている場合はsegfaultにもなります)、これ以上何も提供されないことに注意してください。つまり、保釈するか、何らかの方法で割り当てようとするだけです。

使用するmalloc()には、グローバルを使用してNULLを割り当てます。ポインタがNULLでない場合、使用する前に解放しますmalloc()

realloc()既存のデータをコピーする場合は、一般的なケースとしても使用できます。の後にコピーまたは連結する場合は、ワークアウトする前にポインタを確認する必要がありますrealloc()

3.2.5.2 allocaの利点


4
実際、alloca仕様では、失敗時に無効なポインタを返すとは言われていません(スタックオーバーフロー)未定義の動作があると言われています...そしてmallocの場合、ランダムな無効なポインタではなくNULLを返すと言われています(OK、Linuxオプティミスティックメモリ実装により、役に立たない)。
kriss 2014

@kriss Linuxはあなたのプロセスを殺すかもしれませんが、少なくともそれは未定義の振る舞いにベンチャーしません
craig65535

@ craig65535:未定義の動作という表現は、通常、その動作がCまたはC ++仕様で定義されていないことを意味します。特定のOSまたはコンパイラでランダムまたは不安定になることは決してありません。したがって、UBを「Linux」や「Windows」などのOSの名前に関連付けることは意味がありません。それとは何の関係もありません。
kriss

mallocがNULLを返すか、Linuxの場合、メモリアクセスがプロセスを強制終了することが、allocaの未定義の動作よりも好ましいと言っていました。私はあなたの最初のコメントを誤解したに違いないと思います。
craig65535

3

プロセスで使用できるスタックスペースは限られていmalloc()ます。使用可能なメモリの量よりもはるかに少ないです。

を使用alloca()することで、スタックオーバーフローエラーが発生する可能性が劇的に高まります(運が良ければ、またはそうでなければ不可解なクラッシュ)。


それはアプリケーションに大きく依存します。メモリが制限された組み込みアプリケーションのスタックサイズがヒープよりも大きいことは珍しいことではありません(ヒープがある場合でも)。
EBlake

3

それほどきれいではありませんが、パフォーマンスが本当に重要な場合は、スタックのスペースを事前に割り当てることができます。

すでにメモリの最大サイズが必要であり、オーバーフローチェックを維持したい場合は、次のようにします。

void f()
{
    char array_on_stack[ MAX_BYTES_TO_ALLOCATE ];
    SomeType *p = (SomeType *)array;

    (...)
}

12
char配列は、どのデータ型でも正しく配置されることが保証されていますか?allocaはそのような約束を提供します。
JuhoÖstman、2009

@JuhoÖstman:アラインメントの問題がある場合は、charの代わりにstruct(または任意のタイプ)の配列を使用できます。
kriss 2014

これを可変長配列と呼びます。C90以降ではサポートされていますが、C ++ではサポートされていません。C ++ 03およびC ++ 11でC可変長配列を使用できますか?を
jww 2015

3

alloca関数は素晴らしく、すべての反対者は単にFUDを広めています。

void foo()
{
    int x = 50000; 
    char array[x];
    char *parray = (char *)alloca(x);
}

アレイとパラリーはまったく同じで、リスクはまったく同じです。一方が他方よりも優れていると言うことは、構文的な選択であり、技術的な選択ではありません。

スタック変数とヒープ変数のどちらを選択するかについては、スコープ内のライフタイムを持つ変数に対してヒープオーバースタックを使用して長時間実行するプログラムには多くの利点があります。ヒープの断片化を回避し、未使用の(使用できない)ヒープスペースによってプロセススペースが増大するのを回避できます。クリーンアップする必要はありません。プロセスのスタック割り当てを制御できます。

なぜこれが悪いのですか?


3

実際、allocaがスタックを使用することは保証されていません。実際、allocaのgcc-2.95実装は、malloc自体を使用してヒープからメモリを割り当てます。また、その実装にはバグがあり、さらにgotoを使用してブロック内で呼び出すと、メモリリークや予期しない動作が発生する可能性があります。決してそれを使用すべきではないと言うわけではありませんが、ときどきallocaは、元に戻すよりもオーバーヘッドが多くなることがあります。


まるでgcc-2.95がallocaを壊しているかのように聞こえ、おそらくを必要とするプログラムには安全に使用できませんalloca。がlongjmpフレームを放棄するために使用された場合、どのようにしてメモリをクリーンアップしallocaますか 今日誰がgcc 2.95を使うのですか?
Kaz

2

私見、allocaは悪い習慣と考えられています。なぜなら、誰もがスタックサイズの制限を使い果たすことを恐れているからです。

私はこのスレッドといくつかの他のリンクを読んで多くを学びました:

私は主にallocaを使用して、変更、C89スタイル、#ifdef _MSC_VERなどなしで、プレーンなCファイルをmsvcおよびgccでコンパイルできるようにしています。

ありがとうございました !このスレッドで私はこのサイトにサインアップしました:)


このサイトには「スレッド」のようなものはないことに注意してください。Stack Overflowには、ディスカッションスレッド形式ではなく、質疑応答形式があります。「回答」はディスカッションフォーラムの「返信」とは異なります。それはあなたが実際に質問への回答を提供していることを意味し、他の回答への応答やトピックに関するコメントに使用すべきではありません。50人以上の担当者がいると、コメント投稿できますが、必ず「いつコメントすべきか」を読んでください。セクション。サイトの形式について理解を深めるには、概要ページをお読みください。
Adi Inbar 2014年

1

私の意見では、alloca()は、利用可能な場合、制約された方法でのみ使用する必要があります。「goto」の使用と非常によく似ていますが、それ以外の場合は相当数の人がalloca()の使用だけでなく、存在にも強い嫌悪感を持っています。

組み込み使用の場合、スタックサイズが既知であり、割り当てのサイズに関する規則と分析を介して制限を課すことができ、C99 +をサポートするようにコンパイラーをアップグレードできない場合、alloca()の使用は問題なく、私はそれを使用することが知られています。

使用可能な場合、VLAにはalloca()よりもいくつかの利点があります。配列スタイルのアクセスが使用されている場合、コンパイラは範囲外アクセスをキャッチするスタック制限チェックを生成できます(これを行うコンパイラがあるかどうかはわかりませんが、行われます)、そしてコードの分析により、配列アクセス式が適切にバインドされているかどうかを判断できます。自動車、医療機器、アビオニクスなどの一部のプログラミング環境では、固定サイズのアレイでも、自動(スタック上)と静的割り当て(グローバルまたはローカル)の両方でこの分析を行う必要があることに注意してください。

スタックにデータと戻りアドレス/フレームポインターの両方を格納するアーキテクチャでは(私が知っていることは、それらすべてです)、変数に割り当てられたアドレスが取得される可能性があるため、スタックに割り当てられた変数は危険であり、未チェックの入力値が許可する場合がありますあらゆる種類のいたずら。

埋め込み空間では移植性はそれほど問題ではありませんが、注意深く制御された状況以外でalloca()を使用することはお勧めできません。

埋め込まれたスペースの外では、効率のためにロギングおよびフォーマット関数の内部でalloca()を使用し、一時的構造(alloca()を使用して割り当てられる)がトークン化および分類中に作成され、その後永続的ですオブジェクト(malloc()を介して割り当てられる)は、関数が戻る前に生成されます。小さな一時構造にalloca()を使用すると、永続オブジェクトが割り当てられるときの断片化が大幅に減少します。


1

ここでのほとんどの回答は、主にポイントを逃しています。使用する理由があります _alloca()います。することが、単にスタックに大きなオブジェクトを格納するよりも悪い可能性ががあります。

自動ストレージとの主な違い_alloca()は、後者には追加の(深刻な)問題が発生することです。割り当てられたブロックはコンパイラーによって制御されないため、コンパイラーがそれを最適化またはリサイクルする方法はありません。

比較:

while (condition) {
    char buffer[0x100]; // Chill.
    /* ... */
}

と:

while (condition) {
    char* buffer = _alloca(0x100); // Bad!
    /* ... */
}

後者の問題は明白です。


VLAとの違いを実証する実用的な例はありますかalloca(そうです、私はVLAと言います。これallocaは静的サイズの配列の作成者だけではありません)。
ルスラン

2番目のユースケースには、1番目のユースケースではサポートされていないユースケースがあります。ループが「n」回実行された後、「リンクされたリストまたはツリー」に「n」レコードが必要な場合があります。このデータ構造は、関数が最終的に戻るときに破棄されます。それは私が何かをそのようにコーディングすると言っているわけではありません:-)
greggo

1
そして、「コンパイラーはそれを制御できない」というのは、それがalloca()の定義方法だからです。最近のコンパイラはallocaが何であるかを知っており、それを特別に扱います。80年代のライブラリ関数だけではありません。C99 VLAは基本的に、ブロックスコープ(およびより適切なタイピング)を持つallocaです。多かれ少なかれ制御ではなく、単に異なるセマンティクスに準拠します。
greggo

@greggo:もしあなたが反対投票者なら、私の回答が役に立たないと思う理由を喜んで聞いたでしょう。
alecov

Cでは、リサイクルはコンパイラーのタスクではなく、Cライブラリー(free())のタスクです。alloca()は戻るときに解放されます。
peterh-モニカを

1

誰もこれについて言及しているとは思いませんが、allocaには、mallocに必ずしも存在しないいくつかの深刻なセキュリティ問題があります(これらの問題は、動的であるかどうかにかかわらず、スタックベースの配列でも発生します)。メモリはスタックに割り当てられるため、バッファのオーバーフロー/アンダーフローは、mallocだけの場合よりもはるかに深刻な結果をもたらします。

特に、関数の戻りアドレスはスタックに格納されます。この値が破損した場合、コードがメモリの実行可能領域に移動する可能性があります。コンパイラーはこれを困難にするために(特にアドレスレイアウトをランダム化することにより)非常に長くなります。ただし、戻り値が破損している場合はSEGFAULTが最良のケースであるため、これは単なるスタックオーバーフローよりも明らかに悪いですが、ランダムなメモリの一部、または最悪の場合、プログラムのセキュリティを損なうメモリ領域の実行を開始する可能性もあります。 。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.