11

組み込み環境でスタック/ヒープの衝突と思われる現象が発生しています (背景については、この質問を参照してください)。

ヒープにメモリを割り当てないようにコードを書き直してみたいと思います。

C でヒープを使用せずにアプリケーションを作成できますか? たとえば、動的メモリ割り当てが必要な場合にのみスタックを使用するにはどうすればよいでしょうか?

4

11 に答える 11

30

私は、生物医学機械用の「超安全」コードを書いていた組み込み環境で一度それを行いました。Malloc()は、リソースの制限と動的メモリから取得できる予期しない動作のために、明示的に禁止されていました(malloc()、VxWorks / Tornado、およびフラグメンテーションを探してください。良い例があります)。

とにかく、解決策は、必要なリソースを事前に計画し、「動的」なリソースを別のモジュールに含まれるベクトルに静的に割り当て、ある種の特別な目的のアロケーターにポインターを与えたり戻したりすることでした。このアプローチは、断片化の問題を完全に回避し、リソースが使い果たされた場合に、よりきめ細かいエラー情報を取得するのに役立ちました。

これは大きな鉄ではばかげているように聞こえるかもしれませんが、組み込みシステム、特にセーフティクリティカルなシステムでは、ハードウェアのサイズを決定するためだけであれば、どの時間とスペースのリソースが必要かを事前によく理解しておくことをお勧めします。

于 2009-06-22T12:34:50.790 に答える
9

おかしなことに、静的に割り当てられたメモリに完全に依存しているデータベース アプリケーションを見たことがあります。このアプリケーションには、フィールドとレコードの長さに厳しい制限がありました。組み込みのテキスト エディター (私は今でもそれを呼んで震えています) でさえ、250 行を超えるテキストを含むテキストを作成できませんでした。これで、現時点で抱えていたいくつかの疑問が解決しました: クライアントごとに 40 レコードしか許可されていないのはなぜですか?

重大なアプリケーションでは、実行中のシステムのメモリ要件を事前に計算することはできません。したがって、必要に応じてメモリを動的に割り当てることをお勧めします。それにもかかわらず、組み込みシステムでは、メモリ不足による予期しない障害を防ぐために必要なメモリを事前に割り当てるのが一般的です。

alloca() ライブラリ呼び出しを使用して、スタックに動的メモリを割り当てることができます。ただし、このメモリはアプリケーションの実行コンテキストに密着しており、このタイプのメモリを呼び出し元に返すことはお勧めできません。これは、後のサブルーチン呼び出しによって上書きされるためです。

だから私はあなたの質問に「場合による」とはっきりと答えるかもしれません...

于 2009-06-22T11:51:09.023 に答える
6

スタックにメモリを割り当てる関数を使用できますalloca()。このメモリは、関数を終了すると自動的に解放されます。alloca()は GNU 固有であり、GCC を使用するため、使用可能である必要があります。

を参照してくださいman alloca

もう 1 つのオプションは、可変長配列を使用することですが、C99 モードを使用する必要があります。

于 2009-06-22T12:59:10.523 に答える
3

main() でスタックから大量のメモリを割り当て、後でコードにサブ割り当てさせることができます。プログラムが実際には必要のないメモリを占有していることを意味するため、これは愚かなことです。

ヒープを回避したい理由は思いつきません (ある種のばかげたプログラミングの課題や学習演習を除いて)。ヒープ割り当てが遅く、スタック割り当てが速いと「聞いた」場合、それは単にヒープに動的割り当てが含まれているためです。スタック内の予約済みブロックからメモリを動的に割り当てると、同じように遅くなります。

スタックの「最も若い」アイテムのみを解放できるため、スタックの割り当ては簡単かつ高速です。ローカル変数に対して機能します。動的データ構造では機能しません。

編集:質問の動機を見た...

まず、ヒープとスタックは、同じ量の使用可能なスペースを求めて競合する必要があります。一般に、それらはお互いに向かって成長します。これは、スタックがヒープと衝突するのではなく、何らかの方法ですべてのヒープ使用量をスタックに移動すると、スタック サイズが利用可能な RAM の量を超えることを意味します。

ヒープとスタックの使用状況を監視する必要があるだけだと思います (ローカル変数へのポインターを取得して、スタックが現在どこにあるかを把握できます)。高すぎる場合は、それを減らします。動的に割り当てられた小さなオブジェクトが多数ある場合は、割り当てごとにメモリ オーバーヘッドが発生することに注意してください。そのため、プールからオブジェクトをサブ割り当てすると、メモリ要件を削減できます。どこでも再帰を使用する場合は、配列ベースのソリューションに置き換えることを検討してください。

于 2009-06-22T11:57:29.697 に答える
2

組み込みアプリケーションはメモリの割り当てに注意する必要がありますが、スタックや事前に割り当てられた独自のヒープを使用することが答えだとは思いません。可能であれば、初期化時に必要なすべてのメモリ(通常はバッファと大きなデータ構造)をヒープから割り当てます。これには、私たちのほとんどが今まで使用しているものとは異なるスタイルのプログラムが必要ですが、決定論的な動作に近づくための最良の方法です。

後でサブ割り当てされる大きなヒープは、依然としてメモリ不足の影響を受ける可能性があり、その場合に実行する唯一のことは、ウォッチドッグをキックイン(または同様のアクション)することです。スタックの使用は魅力的に聞こえますが、スタックに大きなバッファー/データ構造を割り当てる場合は、プログラムが実行できるすべての可能なコードパスを処理するのに十分な大きさのスタックであることを確認する必要があります。これは簡単ではなく、最終的にはサブ割り当てヒープに似ています。

于 2009-06-22T13:10:43.650 に答える
2

はい、可能です。動的なニーズをメモリからディスク (または利用可能な大容量ストレージ) に移すと、結果としてパフォーマンスが低下します。

たとえば、未知のサイズのバイナリ ツリーを構築して参照する必要があるとします。ツリーのノードを記述するレコード レイアウトを指定します。他のノードへのポインターは、実際にはツリー ファイル内のレコード番号です。追加のレコードをファイルに書き込むことでツリーに追加し、レコードを読み取り、その子を別のレコード番号として見つけ、そのレコードを読み取るなどしてツリーをたどるルーチンを作成します。

この手法では領域が動的に割り当てられますが、これは RAM 領域ではなくディスク領域です。関連するすべてのルーチンは、静的に割り当てられたスペース (スタック上) を使用して記述できます。

于 2009-06-22T12:03:55.190 に答える
2

ヒープ メモリを使用せずに C で動的メモリ割り当てを行うことはできません。ヒープを使用せずに実際のアプリケーションを作成するのは非常に困難です。少なくとも、これを行う方法は思いつきません。

ところで、なぜヒープを避けたいのですか? それの何がそんなに悪いのですか?

于 2009-06-22T11:49:03.223 に答える
2

1: はい、できます - 動的メモリ割り当てが必要ない場合は、アプリによってはパフォーマンスが低下する可能性があります。(つまり、ヒープを使用しないと、より良いアプリが得られません)

2: いいえ、スタックに動的にメモリを割り当てることはできないと思います。その部分はコンパイラによって管理されているからです。

于 2009-06-22T11:50:10.350 に答える
0

私の最大の関心事は、ヒープを廃止することは本当に役立つのかということです。

ヒープを使用しないという希望はスタック/ヒープの衝突に起因するため、スタックの開始とヒープの開始が適切に設定されていると仮定すると(たとえば、同じ設定で、小さなサンプルプログラムにはそのような衝突の問題はありません)、衝突はハードウェアにプログラムに十分なメモリがありません。

ヒープを使用しない場合、ヒープの断片化から無駄なスペースを節約できる可能性があります。しかし、プログラムが大量の不規則な大きなサイズの割り当てにヒープを使用しない場合、無駄はおそらくそれほど多くありません。衝突の問題は、メモリ不足の問題であり、ヒープを回避するだけでは修正できない問題であることがわかります。

このケースに取り組むための私のアドバイス:

  1. プログラムの潜在的なメモリ使用量の合計を計算します。ハードウェア用に準備したメモリの量に近すぎますが、まだ超えていない場合は、
  2. 使用するメモリを少なくする(アルゴリズムを改善する)か、メモリをより効率的に使用する(たとえばmalloc()、ヒープの断片化を減らすために、より小さく、より通常のサイズにする)ようにしてください。また
  3. ハードウェア用にメモリを追加購入するだけです

もちろん、すべてを事前定義された静的メモリ空間にプッシュしようとすることもできますが、今回は静的メモリにスタック上書きされる可能性が非常に高くなります。したがって、最初にメモリ消費を減らし、次にメモリを購入するようにアルゴリズムを改善します。

于 2009-06-22T13:23:06.560 に答える
0

多くの場合、動的メモリ割り当てを使用せずに組み込みアプリケーションを作成できます。多くの組み込みアプリケーションでは、ヒープの断片化によって問題が発生する可能性があるため、動的割り当ての使用は推奨されていません。時間の経過とともに、メモリを割り当てるのに適切なサイズの空きヒープ領域がなくなる可能性が高くなり、このエラーを処理するためのスキームがない限り、アプリケーションはクラッシュします。これを回避するためのさまざまな方法があります。その 1 つは、常に固定サイズのオブジェクトをヒープに割り当てて、新しい割り当てが常に解放されたメモリ領域に収まるようにすることです。もう 1 つは、割り当ての失敗を検出し、ヒープ上のすべてのオブジェクトに対してデフラグ プロセスを実行するためのものです (読者の課題として残しておきます!)。

使用しているプロセッサまたはツールセットはわかりませんが、多くの場合、スタティック、ヒープ、およびスタックは、リンカーで個別に定義されたセグメントに割り当てられます。この場合、スタックが、定義したメモリ空間の外で成長しているに違いありません。必要な解決策は、ヒープおよび/または静的変数のサイズ (これら 2 つが連続していると仮定) を減らして、スタックに使用できる容量を増やすことです。一方的にヒープを減らすことは可能かもしれませんが、断片化の問題が発生する可能性が高くなる可能性があります。不必要な静的変数がないことを確認すると、変数が自動にされた場合にスタックの使用量が増加する可能性がありますが、スペースがいくらか解放されます。

于 2009-06-23T09:22:17.973 に答える
0

私はこの問題を別の方法で攻撃します.スタックとヒープが衝突していると思われる場合は、それを防ぐことでこれをテストしてください.

たとえば、(*ix システムを想定して)mprotect()最後のスタック ページ (固定サイズのスタックを想定) を試してみると、アクセスできなくなります。または - スタックが大きくなる場合 -mmapスタックとヒープの中間にあるページ。ガード ページで segv を取得した場合は、スタックまたはヒープの最後を使い果たしたことがわかります。セグメント障害のアドレスを見ると、スタックとヒープのどちらが衝突したかがわかります。

于 2009-06-22T18:48:26.453 に答える