malloc pointer




mallocの後で解放しないと本当に何が起こるのですか? (12)

ここで本当の結果は何ですか?

プログラムがメモリをリークしました。 お使いのOSによっては、復旧された可能性があります。

ほとんどの現代のデスクトップオペレーティングシステム 、プロセスの終了時に漏れたメモリを回復しますが、他の多くの回答からも分かるように、この問題を無視するのは残念です。

しかし、あなたは頼りにすべきではない安全機能に頼っており、プログラム(または関数)は、この動作 次回は "ハード"なメモリリークを引き起こすシステム上で動作する可能性があります。

カーネルモードで動作している場合や、メモリ保護機能を使用していないヴィンテージ/組み込みオペレーティングシステムで動作している場合があります。 (MMUはダイスペースを占有し、メモリ保護はCPUサイクルを追加しますが、プログラマーが自分自身でクリーンアップすることはあまりありません)。

メモリは、好きなように使用して再利用できますが、終了する前にすべてのリソースの割り当てを解除してください。

これは何年も前から私を悩ませてきました。

私たちはすべて、割り当てられたすべてのポインタを解放しなければならないと学校で教えられています(少なくとも私はそうでした)。 私はちょっと不思議ですが、メモリを解放しないという実際のコストについては、ちょっと不思議です。 いくつかの明白なケースでは、 mallocがループ内またはスレッド実行の一部として呼び出されたときのように、解放することは非常に重要であり、メモリリークはありません。 しかし、次の2つの例を考えてみましょう。

まず、次のようなコードがあるとします。

int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}

ここで本当の結果は何ですか? 私の考えは、プロセスが死んでしまった後、ヒープスペースが無くなってしまって、 freeコールを逃すことに何の害もないからです(しかし、とにかくクローズ、保守性、そして良い習慣のためにそれを持っていることの重要性を認識しています)。 私はこの考えの中で正しいのでしょうか?

次に、シェルのように動作するプログラムがあるとしましょう。 ユーザーはaaa = 123ような変数を宣言でき、後で使用できるように動的なデータ構造に格納されます。 明らかに、* alloc関数(ハッシュマップ、リンクリストなど)を呼び出すいくつかのソリューションを使用することは明らかです。 この種のプログラムでは、 mallocを呼び出した後で自由にするのは意味がありません。なぜなら、これらの変数はプログラムの実行中に常に存在しなければならず、静的に割り当てられた空間でこれを実装する良い方法はないからです。 割り当てられているが、プロセスの終了の一部として解放されただけのメモリを持つのは悪い設計ですか? もしそうなら、代替案は何ですか?


あなたは正しいです、害はされていません。

これにはさまざまな理由があります。

  • すべてのデスクトップおよびサーバー環境は、単にexit()でメモリスペース全体を解放します。 彼らはヒープのようなプログラム内部データ構造を知らない。

  • ほとんどすべてのfree()実装 、とにかにオペレーティングシステムにメモリを返すことはありません。

  • もっと重要なのは、exit()の直前に完了すると時間が無駄だということです。 終了時には、メモリページとスワップ領域が解放されます。 対照的に、一連のfree()呼び出しはCPU時間を消費し、ディスク・ページング操作、キャッシュ・ミス、およびキャッシュの追いやりを引き起こします。

無意味なopsの確実性を正当化する将来のコード再利用の可能に関して:これは考慮すべきだが、間違いなくAgileな方法ではない。 YAGNI!


OSTEPオンライン教科書には、実際にあなたの質問について正確に議論するオペレーティングシステムの学部コースのセクションがあります。

関連するセクションは、6ページの「 メモリAPI 」の章の 「メモリを解放する」を参照してください。

場合によっては、free()を呼び出すのが合理的であるように見えるかもしれません。 たとえば、あなたのプログラムは短命であり、すぐに終了します。 この場合、プロセスが終了すると、OSは割り当てられたすべてのページをクリーンアップし、したがってメモリリーク自体は発生しません。 これは確かに「効果があります」(7ページを参照)が、開発するのはおそらく習慣が悪いので、そのような戦略を選択することには注意してください

この抜粋は、仮想メモリの概念を導入する文脈の中にあります。 基本的には、本書のこの時点で、著者は、オペレーティングシステムの目標の1つが「メモリを仮想化する」こと、すなわちすべてのプログラムが非常に大きなメモリアドレス空間にアクセスできると信じさせることであると説明しています。

背後では、オペレーティングシステムは、ユーザーが見る「仮想アドレス」を物理メモリを指す実際のアドレスに変換します。

ただし、物理メモリなどのリソースを共有するには、オペレーティングシステムがプロセスを使用しているプロセスを追跡する必要があります。 したがって、プロセスが終了すると、オペレーティングシステムの機能と設計目標の範囲内でプロセスのメモリを再利用して、他のプロセスとメモリを再分配して共有できるようになります。

編集:抜粋に記載されている脇にコピーされます。

ASIDE: あなたのプロセスの終了時にメモリが解放されない理由

短命のプログラムを書くときには、 malloc()を使っていくらかの領域を割り当てるかもしれません。 プログラムは実行され、完了しようとしています。終了する直前にfree()を呼び出す必要がありますか? それが間違っているように見えますが、実際の意味では記憶が失われません。 理由は簡単です。実際には、システムには2つのレベルのメモリ管理があります。 第1レベルのメモリ管理は、実行時にプロセスにメモリを引き渡すOSによって実行され、プロセスが終了する(または終了する)ときにメモリを取り戻します。 第2レベルの管理は、 malloc()free()を呼び出すときにヒープ内など、各プロセス内にあります。 free()を呼び出すことができなくても、ヒープ内のメモリがリークしても、オペレーティングシステムは、プログラムのすべてのメモリ(コード、スタック、ここではヒープに関連するページを含む)を再利用します実行が終了しました。 アドレス空間内のヒープの状態が何であっても、OSはプロセスが終了したときにそれらのページをすべて取り戻すので、解放していないにもかかわらずメモリが失われないことが保証されます。

したがって、短命のプログラムでは、メモリが漏れても操作上の問題が発生しないことがよくあります(ただし、悪い形であると考えられます)。長時間実行しているサーバー(Webサーバーやデータベース管理システムなど、決して終了しないサーバーなど)を作成すると、漏れたメモリがはるかに大きな問題になり、アプリケーションがメモリ不足になった場合にクラッシュする可能性があります。もちろん、メモリのリークは、オペレーティングシステムそのものの1つの特定のプログラム内でのさらに大きな問題です。私たちをもう一度見せてください:カーネルコードを書く人は、すべての中で最も厳しい仕事をしています...

メモリAPIの章の7ページから

OSTEP
Remzi H. Arpaci-DusseauとAndrea C. Arpaci-Dusseau Arpaci-Dusseau 2015年3月版(バージョン0.90)


あなたが割り当てたメモリを使用しているなら、あなたは間違ったことはしていません。 メモリを解放せずに割り付け、残りのプログラムで使用できるようにすることなく、メモリを割り当てる関数(main以外)を書くときに問題になります。 その後、あなたのプログラムは割り当てられたメモリで動作を続けますが、それを使う方法はありません。 あなたのプログラムや実行中のプログラムはそのメモリを奪われています。

編集:他の実行中のプログラムがそのメモリを奪われていると言うのは100%正確ではありません。 オペレーティングシステムは、プログラムを仮想メモリ( </handwaving> )に交換することを犠牲にして、いつでもそれを使用させることができます。 ただし、プログラムが使用していないメモリを解放すると、仮想メモリスワップが必要になる可能性は低くなります。


あなたは正しいです、プロセスが終了するとメモリは自動的に解放されます。 プロセスが終了したときに大規模なクリーンアップを行わないように努めている人もいます。なぜなら、それらはすべてオペレーティングシステムに放棄されるからです。 しかし、プログラムの実行中は、未使用のメモリを解放する必要があります。 そうしないと、作業セットが大きくなり過ぎると、最終的に実行されたり、過度のページングが発生する可能性があります。


このコードは通常正しく動作しますが、コードの再利用の問題を考慮してください。

割り当てられたメモリを解放しないコードスニペットを作成したことがあります。メモリが自動的に再利用されるように実行されます。 大丈夫だと思う

それから、他の誰かがあなたのプロジェクトにあなたのスニペットを、毎秒1000回実行されるようにコピーします。 その人は今、彼のプログラムに大きなメモリリークを持っています。 一般的にはあまり良くありません。通常、サーバーアプリケーションでは致命的です。

コードの再利用は、企業では一般的です。 通常、会社は従業員が生産するすべてのコードを所有しており、すべての部門が会社の所有物を再利用する可能性があります。 したがって、このような「無邪気に見える」コードを書くことによって、他の人に頭痛を引き起こす可能性があります。 これはあなたを解雇させるかもしれません。


アプリケーションを最初から開発している場合は、無料でいつ呼び出すべきかについて、教育的な選択をすることができます。 あなたのサンプルプログラムはうまくいきます:それはメモリを割り当てます、多分それは数秒間働いた後、閉じて、それが主張したすべてのリソースを解放します。

あなたが何か他のものを書いているのであれば、サーバー/長年走っているアプリケーションか、他の人が使うライブラリであれば、あなたはmallocのすべてを無料で呼び出すことを期待するべきです。

実用的な面を一瞬無視して、より厳しいアプローチに従うことはずっと安全です。あなたがmallocをすべて解放するように強制してください。 コードを記述するたびにメモリリークを監視する習慣がなければ、簡単にリークが発生する可能性があります。 つまり言い換えれば、はい、あなたはそれなしで逃げることができます。 しかし、注意してください。


変数を解放しないで本当の危険はありませんが、最初のブロックを解放せずにメモリブロックへのポインタを別のメモリブロックに割り当てると、最初のブロックはアクセスできなくなりますが、それでも領域を占有します。 これがメモリリークと呼ばれるものです。これを規則正しく行うと、プロセスは他のプロセスからシステムリソースを奪い、ますます多くのメモリを消費し始めます。

プロセスが短命であれば、処理が完了したときに割り当てられたすべてのメモリがオペレーティングシステムによって再利用されるため、この処理をやめることができますが、それ以上使用しないすべてのメモリを解放することをお勧めします。


私は、OPが正しかったり、害がないと言う皆さんとは全く反対しています。

誰もが近代的なOSやレガシーOSについて話しています。

しかし、OSがない環境にいるとどうなりますか? どこにも何もない?

現在、スレッド型の割り込みを使用してメモリを割り当てているとします。 Cの標準ISO / IEC:9899は、以下のように記述されたメモリの寿命です。

7.20.3メモリ管理機能

1 calloc、malloc、およびrealloc関数への連続呼び出しによって割り当てられた記憶域の順序と連続性は指定されていません。 割り当てが成功した場合に返されるポインタは、任意のオブジェクト型へのポインタに代入されるように適切に配置され、そのようなオブジェクトまたはそのオブジェクトの配列へのアクセスに使用されます(領域が明示的に割り当て解除されるまで) 。 割り当てられたオブジェクトの存続期間は、割り振りから割り振り解除まで拡張されます。[...]

したがって、環境があなたのために自由な仕事をしているということは認められていません。 それ以外の場合は、最後の文に追加されます。「またはプログラムが終了するまで」

言い換えれば、記憶を解放しないことは悪い習慣ではない。 これは非互換で、C準拠のコードを生成しません。 [...]が環境によってサポートされている場合、少なくとも正しいと見なすことができます。

しかし、OSがまったくない場合は、誰もあなたのために仕事をしているわけではありません(私は一般に、組み込みシステムにメモリを割り当てたり、割り当てを変更したりしませんが、あなたが望むかもしれません。

したがって、一般的なプレーンC(OPがタグ付けされている)で言えば、これは単に誤った移植性のないコードを生成するだけです。


私は通常、割り当てられたすべてのブロックを解放したら、それを済ませたと確信しています。 今日、私のプログラムのエントリーポイントはmain(int argc, char *argv[])かもしれませんが、明日はfoo_entry_point(char **args, struct foo *f)なり、関数ポインタとしてタイプされます。

だから、もし起これば、私は今、漏れがあります。

あなたの2番目の質問に関して、私のプログラムがa = 5のような入力を受け取った場合、私はaのためのスペースを割り当てるか、後続のa = "foo"に同じスペースを再割り当てします。 これは以下の場合に割り当てられる。

  1. ユーザーが「unset a」と入力した
  2. 私のクリーンアップ機能が入力されました。信号を処理するか、ユーザーが「quit」と入力して、

私はプロセスが終了した後にメモリを再利用しない現代的な OSは考えられません。 そして再び、free()は安く、なぜクリーンアップしないのですか? 他の人が言っているように、valgrindのようなツールは、本当に心配する必要があるリークを見つけるのに最適です。 たとえあなたのブロックが「まだ到達可能」とラベル付けされていても、あなたが漏れがないことを保証しようとしているときに、出力に余分なノイズがあります。

もう一つの神話は、「 もしそれが本気であれば、私はそれを解放する必要はない 」というのは間違っている。 次の点を考慮してください。

char *t;

for (i=0; i < 255; i++) {
    t = strdup(foo->name);
    let_strtok_eat_away_at(t);
}

それがフォーク/デーモン化(そして理論上は永遠に実行される)の前に来たのであれば、あなたのプログラムは255ミリ秒の未定サイズを漏らしてしまっただけです。

良い、よく書かれたプログラムは、常にそれ自身の後にクリーンアップする必要があります。 すべてのメモリを解放し、すべてのファイルをフラッシュし、すべてのディスクリプタを閉じる、すべての一時ファイルのリンクを解除するなどの処理を行う必要があります。クラッシュを検出して再開します。

本当に、あなたが他のものに移動するときにあなたのものを維持しなければならない貧しい魂に優しい..それらに手を貸す 'valgrindクリーン' :)


プログラムがオペレーティングシステムを終了する前に数メガバイトを解放するのを忘れた場合、それらは解放されます。しかし、あなたのプログラムが一度に数週間実行され、プログラム内のループが各繰り返しで数バイトを解放するのを忘れた場合、通常のコンピュータで再起動しない限り、コンピュータの使用可能なメモリをすべて使い果たしてしまいますたとえ当初は設計されていなかったとしても、プログラムが真剣に大きなタスクに使用されていれば、小さなメモリリークさえも悪いかもしれません。


私は、あなたの2つの例は実際には1つしかないと考えています。free()プロセスの終了時に発生する必要があります。プロセスが終了してから無駄です。

しかし、2番目の例では、唯一の違いは、未定義の数を許可するmalloc()ことで、メモリ不足につながる可能性があることです。状況を処理する唯一の方法は、戻りコードをチェックし、malloc()それに応じて行動することです。





free