3
int a = 0; 
int *b = malloc (sizeof(int));
b = malloc (sizeof(int));

上記のコードは、ヒープにメモリを割り当ててから解放しないため、問題があります。つまり、ヒープにアクセスできなくなります。ただし、「a」も作成して使用しなかったため、スタックにメモリを割り当てましたが、これはスコープが終了するまで解放されません。

では、ヒープ上のメモリを解放しないのが悪い習慣であるのに、(スコープが終了するまで) スタック上のメモリを解放しなくてもよいのはなぜでしょうか?

注: スタック上のメモリを解放できないことはわかっています。なぜそれが悪いと見なされないのかを知りたいのです。

4

7 に答える 7

8

スコープが終了すると、スタック メモリは自動的に解放されます。ヒープに割り当てられたメモリは、明示的に解放しない限り占有されたままになります。例として:

void foo(void) {
    int a = 0;
    void *b = malloc(1000);
}

for (int i=0; i<1000; i++) {
    foo();
}

このコードを実行すると、 が必要とする使用可能なメモリが 1000*1000 バイト減少しますが、 がb必要とするメモリaは、呼び出しから戻ったときに常に自動的に解放されますfoo

于 2013-11-05T15:32:34.730 に答える
5

シンプル:メモリリークするからです。そして、メモリリークは悪いです。リーク: 悪い、フリー: 良い。or 、または実際に *alloc 関数
を呼び出すときは、メモリのチャンクを要求しています (そのサイズは、割り当て関数に渡される引数によって定義されます)。malloccalloc

プログラムが持っているメモリの一部に存在するスタック変数とは異なり、自由に支配され、同じルールがヒープメモリには適用されません。さまざまな理由でヒープ メモリを割り当てる必要がある場合があります。スタックが十分に大きくない、ポインタの配列が必要であるが、コンパイル時にこの配列が必要な大きさを知る方法がない、共有する必要があるメモリの一部 (スレッド化の悪夢)、プログラム内のさまざまな場所 (関数) でメンバーを設定する必要がある構造体...

これらの理由のいくつかは、その性質上、メモリへのポインタが範囲外になるとすぐにメモリを解放できないことを意味します。同じメモリ ブロックを指す別のポインターが、別のスコープ内にまだ存在している可能性があります。
ただし、コメントの 1 つに記載されているように、これにはわずかな欠点があります。ヒープ メモリは、プログラマ側でより多くの認識を必要とするだけでなく、スタックでの作業よりも高価で遅くなります。
したがって、いくつかの経験則は次のとおりです。

  • あなたはメモリを要求したので、それを大事に扱います... 遊んだ後は解放されていることを確認してください。
  • 正当な理由がない限り、ヒープ メモリを使用しないでください。たとえば、スタック オーバーフローを回避することは正当な理由です。

とにかく、いくつかの例:
スタック オーバーフロー:

#include <stdio.h>
int main()
{
    int foo[2000000000];//stack overflow, array is too large!
    return 0;
}

ここではスタックを使い果たしたので、ヒープにメモリを割り当てる必要があります。

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *foo= malloc(2000000000*sizeof(int));//heap is bigger
    if (foo == NULL)
    {
        fprintf(stderr, "But not big enough\n");
    }
    free(foo);//free claimed memory
    return 0;
}

または、長さがユーザー入力に依存する配列の例:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *arr = NULL;//null pointer
    int arrLen;
    scanf("%d", &arrLen);
    arr = malloc(arrLen * sizeof(int));
    if (arr == NULL)
    {
        fprintf(stderr, "Not enough heap-mem for %d ints\n", arrLen);
        exit ( EXIT_FAILURE);
    }
    //do stuff
    free(arr);
    return 0;
}

リストは続きます... mallocorcallocが役立つ別のケース: 文字列の配列で、すべてのサイズが異なる場合があります。比較:

char str_array[20][100];

この場合str_arrayは、それぞれ 100 文字の長さの 20 文字の配列 (または文字列) の配列です。しかし、必要な最大文字数が 100 文字で、平均して 25 文字以下しか使用しないとしたらどうでしょうか。
C で書いているのは、高速であり、プログラムが実際に必要とする以上のリソースを使用しないためですか? それでは、これはあなたが実際にやりたいことではありません。おそらく、次のことが必要です。

char *str_array[20];
for (int i=0;i<20;++i) str_array[i] = malloc((someInt+i)*sizeof(int));

の各要素には、str_array必要なメモリ量が正確に割り当てられています。その方がずっときれいです。ただし、この場合、呼び出しfree(str_array)はそれをカットしません。もう 1 つの経験則は次のとおりです。各 alloc 呼び出しには、freeそれに一致する呼び出しが必要なので、このメモリの割り当て解除は次のようになります。

for (i=0;i<20;++i) free(str_array[i]);

注:
メモリ リークの原因は、動的に割り当てられたメモリだけではありません。それは言わなければならない。ファイルを読み取る場合、 を使用してファイル ポインタを開きますfopenが、そのファイル ( ) を閉じるのに失敗するfcloseと、リークも発生します。

int main()
{//LEAK!!
    FILE *fp = fopen("some_file.txt", "w");
    if (fp == NULL) exit(EXIT_FAILURE);
    fwritef(fp, "%s\n", "I was written in a buggy program");
    return 0;
}

コンパイルして正常に実行されますが、リークが含まれており、1 行追加するだけで簡単にプラグインできます (プラグインする必要があります)。

int main()
{//OK
    FILE *fp = fopen("some_file.txt", "w");
    if (fp == NULL) exit(EXIT_FAILURE);
    fwritef(fp, "%s\n", "I was written in a bug-free(?) program");
    fclose(fp);
    return 0;
}

余談ですが、スコープが非常に長い場合は、1 つの関数に詰め込みすぎている可能性があります。そうでない場合でも、要求されたメモリはいつでも解放できます。現在のスコープの最後である必要はありません。

_Bool some_long_f()
{
    int *foo = malloc(2000000000*sizeof(int));
    if (foo == NULL) exit(EXIT_FAILURE);
    //do stuff with foo
    free(foo);
    //do more stuff
    //and some more
    //...
    //and more
    return true;
}
于 2013-11-05T15:41:32.483 に答える
2

他の回答で何度も言及されているstackheapは、C プログラマーの間でも誤解されることがあるため、そのトピックについて議論する素晴らしい会話があります....

では、ヒープ上のメモリを解放しないのが悪い習慣であるのに、(スコープが終了するまで) スタック上のメモリを解放しなくてもよいのはなぜでしょうか?

自動変数に割り当てられたメモリなど、スタック上のメモリは、それらが作成されたスコープを終了すると自動的に解放されます。scopeグローバル ファイル、関数、または関数内のブロック ( {...} ) 内のいずれを意味するか。
ただし、 、 を使用して作成されたヒープ上のメモリmalloc()、または、を使用して明示的に解放するまで他の目的で使用できないメモリ リソースを割り当てることcalloc()さえありますfopen()free()fclose()

メモリを解放せずに割り当てることがなぜ悪いことなのかを説明するために、アプリケーションが非常に長い時間自律的に実行されるように設計されているとどうなるかを考えてみましょう。たとえば、アプリケーションが車のクルーズ コントロールを制御する PID ループで使用されたとします。また、そのアプリケーションには解放されていないメモリがあり、3 時間実行した後、マイクロプロセッサで使用可能なメモリが使い果たされ、PID が突然レールに落ちました。「ああ!」、「これは決して起こらないでしょう!」とあなたは言います。 はい、そうです。(ここを見てください) . (まったく同じ問題ではありませんが、アイデアは得られます)

その単語の図でうまくいかない場合は、自分の PC でこのアプリケーションを (メモリ リークを使用して) 実行するとどうなるかを観察してください。(少なくとも下の図を見て、それが私のもので何をしたかを確認してください)

お使いのコンピューターは、最終的に動作を停止するまで、ますます動作が遅くなります。おそらく、通常の動作を復元するために再起動する必要があります。
(実行はお勧めしません)

#include <ansi_c.h>
char *buf=0;

int main(void)
{
    long long i;
    char text[]="a;lskdddddddd;js;'";

    buf = malloc(1000000);

    strcat(buf, "a;lskdddddddd;js;dlkag;lkjsda;gkl;sdfja;klagj;aglkjaf;d");
    i=1;    
    while(strlen(buf) < i*1000000)
    {
        strcat(buf,text); 
        if(strlen(buf) > (i*10000) -10)
        {
            i++;
            buf = realloc(buf, 10000000*i); 
        }
    }
    return 0;   
}  

このメモリ ピッグを 30 秒実行した後のメモリ使用量:

ここに画像の説明を入力

于 2013-11-05T16:11:59.160 に答える
0

問題は、ヒープに割り当てたメモリは、明示的に解放しない限り、プログラムが終了するまで解放されないことです。つまり、ヒープ メモリを割り当てるたびに、使用可能なメモリがどんどん減り、最終的にプログラムが (理論的には) 使い果たされることになります。

スタック メモリは、コンパイラによって決定される予測可能なパターンでレイアウトされ、使用されるため、異なります。特定のブロックで必要に応じて拡張し、ブロックが終了すると収縮します。

于 2013-11-05T15:38:32.923 に答える
0

私はそれが本当に頻繁に(関数の最後に)スコープの「終了」に関係しているa思いbます.によって使用される実行メモリab

その関数を数回呼び出してみると、すぐにすべてのメモリを使い果たします。これは、スタック変数では決して起こりません (欠陥のある再帰の場合を除く)。

于 2013-11-05T15:33:03.340 に答える
0

ローカル変数のメモリは、(フレーム ポインターをリセットすることによって) 関数を終了すると自動的に再利用されます。

于 2013-11-05T15:33:16.250 に答える
0

では、ヒープ上のメモリを解放しないのが悪い習慣であるのに、(スコープが終了するまで) スタック上のメモリを解放しなくてもよいのはなぜでしょうか?

次のことを想像してください。

while ( some_condition() )
{
  int x;
  char *foo = malloc( sizeof *foo * N );

  // do something interesting with x and foo
}

xとは両方ともfoo( auto「スタック」) 変数です。 論理的に言えば、それぞれの新しいインスタンスが作成され、各ループ反復で破棄されます1。このループが何回実行されても、プログラムはそれぞれの 1 つのインスタンスに十分なメモリしか割り当てません。

ただし、ループのたびに、N バイトがヒープから割り当てられ、それらのバイトのアドレスが に書き込まれfooます。変数 fooはループの最後で存在しなくなりますが、そのヒープ メモリは割り当てられたままになり、それへの参照が失われたため、割り当てられなくなりfreeます。そのため、ループが実行されるたびに、別の N バイトのヒープ メモリが割り当てられます。時間の経過とともにヒープ メモリが不足し、コードがクラッシュしたり、プラットフォームによってはカーネル パニックが発生したりする可能性があります。それ以前でも、同じマシンで実行されているコードまたは他のプロセスのパフォーマンスが低下する場合があります。

Web サーバーのような長時間実行されるプロセスの場合、これは致命的です。あなたはいつも自分の後片付けを確実にしたいと思っています。スタックベースの変数はクリーンアップされますが、完了後にヒープをクリーンアップする責任があります。


1. 実際には、これは (通常) そうではありません。生成されたマシン コードを見ると、(通常)関数のエントリにx割り当てられたスタック領域が表示されます。foo通常、(関数内のスコープに関係なく) すべてのローカル変数用のスペースが一度に割り当てられます。

于 2013-11-05T16:12:12.227 に答える