5

私は現在、Cベースのアプリケーションに取り組んでおり、アンチパターン以外の方法でメモリを解放することに少しこだわっています。私はメモリ管理のアマチュアです。

私の主な問題は、さまざまなスコープでメモリ構造を宣言することです。これらの構造は、他の関数を参照して渡されます。これらの関数の一部は、エラーとexit()をスローする可能性があります。

1つのスコープでexit()を実行したが、すべてのデータ構造がそのスコープに含まれているわけではない場合、構造を解放するにはどうすればよいですか?

すべてを疑似例外ハンドラーにまとめて、ハンドラーに解放を処理させる必要があると感じますが、解放する必要があるかどうかにかかわらず、すべてを知る必要があるため、それでも醜いようです...

4

8 に答える 8

3

exit()が呼び出されたときにメモリを解放することを心配する必要はありません。プロセスが終了すると、オペレーティングシステムは関連するすべてのメモリを解放します。

于 2009-03-28T03:26:03.740 に答える
3

この質問に適切に答えるには、プログラム全体 (またはシステムなど) のアーキテクチャについて知る必要があると思います。

答えは、場合によります。使用できる戦略はいくつかあります。

他の人が指摘しているように、最新のデスクトップまたはサーバー オペレーティング システムではexit()、プログラムが割り当てたメモリについて心配する必要はありません。

exit()この戦略は、たとえば、すべてがクリーンアップされない可能性がある組み込みオペレーティング システムで開発している場合に変わります。通常、私が目にするのは、個々の関数がエラーのために戻ったときに、自分自身が割り当てたものを確実にクリーンアップすることです。exit()たとえば、10 個の関数を呼び出した後は、呼び出しは表示されません。各関数は、返されたときにエラーを示し、各関数はそれ自体の後にクリーンアップします。元のmain()関数 (必要に応じて、呼び出されない可能性がありますmain()) がエラーを検出し、割り当てられたメモリをクリーンアップして、適切なアクションを実行します。

スコープ内のスコープしかない場合、それはロケット科学ではありません。複数の実行スレッドがあり、データ構造を共有している場合は、困難になります。次に、ガベージ コレクターまたは参照をカウントし、構造体の最後のユーザーがそれを使用したときにメモリを解放する方法が必要になる場合があります。たとえば、BSD ネットワーク スタックのソースを見るとrefcnt、一部の構造体で (参照カウント) 値を使用していることがわかります。これらの構造体は、長期間「生きた」状態に保ち、さまざまなユーザー間で共有する必要があります。 . (これは基本的に、ガベージ コレクターも同様です。)

于 2009-03-28T04:23:05.850 に答える
3

ラッパーを適切mallocな方法で使用することを検討してください。割り当てたメモリを追跡し(おそらくリンクされたリストで)、ラッパーを使用して終了し、メモリを列挙して解放します。リンク リスト構造の追加のパラメーターとメンバーを使用して、メモリに名前を付けることもできます。割り当てられたメモリがスコープに大きく依存するアプリケーションでは、メモリ リークが発生していることに気付くでしょう。これは、メモリをダンプして分析するための良い方法です。

更新: アプリケーションでスレッド化すると、これが非常に複雑になります。スレッドの問題に関する他の回答を参照してください。

于 2009-03-28T04:08:03.140 に答える
1

スコープ/関数間で共有される malloc されたメモリ用の単純なメモリ マネージャーを作成できます。

malloc するときに登録し、解放するときに登録解除します。exit を呼び出す前に、登録されているすべてのメモリを解放する関数を用意します。

多少のオーバーヘッドが追加されますが、メモリを追跡するのに役立ちます。また、厄介なメモリ リークを突き止めるのにも役立ちます。

于 2009-03-28T04:13:17.293 に答える
1

エラーが発生した場合にコードを終了 (または中止) するだけであれば、おそらくメモリの解放について心配する必要はないことを人々はすでに指摘しています。念のため、エラーが発生した場合にリソースを作成および破棄するために、私が開発してよく使用するパターンを次に示します。注: ここでは、実際のコードを書くのではなく、要点を明確にするためにパターンを示しています。

int foo_create(foo_t *foo_out) {
    int res;
    foo_t foo;
    bar_t bar;
    baz_t baz;
    res = bar_create(&bar);
    if (res != 0)
        goto fail_bar;
    res = baz_create(&baz);
    if (res != 0)
        goto fail_baz;
    foo = malloc(sizeof(foo_s));
    if (foo == NULL)
        goto fail_alloc;
    foo->bar = bar;
    foo->baz = baz;
    etc. etc. you get the idea
    *foo_out = foo;
    return 0; /* meaning OK */

    /* tear down stuff */
fail_alloc:
    baz_destroy(baz);
fail_baz:
    bar_destroy(bar);
fail_bar:
    return res; /* propagate error code */
}

きっと、「goto を使っているからダメだ」というコメントが返ってきます。しかし、これは goto の規則的で構造化された使用法であり、一貫して適用されれば、コードがより明確でシンプルになり、保守が容易になります。それなしでは、コードを介して単純で文書化されたティアダウン パスを実現することはできません。

これを実際に使用されている商用コードで確認したい場合は、たとえば、 MPS (偶然にもメモリ管理システム)の arena.c を見てください。

これは貧乏人の try...finish ハンドラのようなもので、デストラクタに少し似たものを提供します。

今では白ひげのように聞こえるかもしれませんが、他の人の C コードに長年取り組んできた私は、特にネットワーク コードやその他の信頼できない状況では、明確なエラー パスの欠如が非常に深刻な問題になることがよくあります。それらを紹介することで、かなりのコンサルタント収入が得られることもあります。

あなたの質問については、他にも言いたいことがたくさんあります。役に立つ場合に備えて、このパターンのままにしておきます。

于 2011-07-14T20:28:59.897 に答える
1

Michael のアドバイスは適切です。終了する場合は、システムがメモリを再利用するため、メモリの解放について心配する必要はありません。

その例外の 1 つは、共有メモリ セグメントです。少なくとも System V 共有メモリでは。これらのセグメントは、それらを作成したプログラムよりも長く存続できます。

これまでに言及されていないオプションの 1 つは、 standard の上に構築されたアリーナベースのメモリ割り当てスキームを使用することですmalloc()。アプリケーション全体が単一のアリーナを使用している場合、クリーンアップ コードはそのアリーナを解放でき、すべてが一度に解放されます。(APR - Apache Portable Runtime - 似ていると思われるプール機能を提供します。David Hanson の「C Interfaces and Implementations」は、アリーナベースのメモリ割り当てシステムを提供します。必要に応じて使用できるものを書きました。)これは「貧乏人のガベージコレクション」と考えることができます。

一般的なメモリ規律として、メモリを動的に割り当てるたびに、どのコードがメモリを解放するのか、いつ解放できるのかを理解する必要があります。標準的なパターンがいくつかあります。最も単純なのは、「この関数で割り当てられ、この関数が戻る前に解放される」というものです。これにより、メモリの大部分を制御し (メモリ割り当てを含むループであまり多くの反復を実行しない場合)、スコープを設定して、現在の関数とそれが呼び出す関数で使用できるようにします。明らかに、呼び出す関数がデータへのポインターを追い払う (キャッシュする) ことはなく、メモリを解放して再利用した後でそれらを再利用しようとしないことを合理的に確認する必要があります。

次の標準パターンは、 と によって例示されfopen()ますfclose()。ポインターをメモリに割り当てる関数があります。これは、呼び出し元のコードで使用でき、プログラムが終了したときに解放されます。ただし、これは多くの場合、最初のケースと非常に似ています。通常は、呼び出し元fclose()の関数も呼び出すことをお勧めしfopen()ます。

残りの「パターン」のほとんどは、いくぶんアドホックです。

于 2009-03-28T05:39:06.773 に答える
0

非常に簡単に言えば、参照カウントの実装を持たない理由は、オブジェクトを作成して渡すときに、参照カウント数をインクリメントおよびデクリメントすることです (複数のスレッドがある場合は、アトミックであることを忘れないでください)。

そうすることで、オブジェクトが使用されなくなったとき (ゼロ参照)、安全に削除するか、参照カウントのデクリメント呼び出しで自動的に削除することができます。

于 2009-03-28T05:30:13.287 に答える
0

これは、Boehm ガベージ コレクターのタスクのように思えます。

http://www.hpl.hp.com/personal/Hans_Boehm/gc/

もちろん、それを使用する余裕があるかどうかは、システムによって異なります。

于 2009-11-13T06:26:43.260 に答える