2

これは私のコードです:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void getinfo(unsigned int a, unsigned int b, char **pStr);

int main(){
    unsigned int len_max = 8;
    unsigned int current_size = 0;
    current_size = len_max;
    char *host, *user;
    char *pStr = malloc(len_max);
    if(pStr == NULL){
        perror("\nMemory allocation\n");
        return EXIT_FAILURE;
    }
    printf("Inserisci hostname: ");
    getinfo(len_max, current_size, &pStr);
    if((host=malloc(strlen(pStr)+1 * sizeof(char))) == NULL) abort();
    strncpy(host, pStr, strlen(pStr)+1);
    printf("Inserisci username: ");
    getinfo(len_max, current_size, &pStr);
    if((user=malloc(strlen(pStr)+1 * sizeof(char))) == NULL) abort();
    strncpy(user, pStr, strlen(pStr)+1);
    printf("\nHostname: %s\nUsername: %s\n", host, user);
    free(pStr);
    free(host);
    free(user);
    return EXIT_SUCCESS;
}

void getinfo(unsigned int a, unsigned int b, char **pStr){
    unsigned int i = 0;
    int c = EOF;
    while((c = getchar()) != '\n'){
        (*pStr)[i++] = (char)c;
        if(i == b){
            b = i+a;
            if((*pStr = realloc(*pStr, b)) == NULL){
                perror("\nMemory allocation error\n");
                exit(EXIT_FAILURE);
            }
        }
    }
    (*pStr)[i]='\0';
}

問題は、realloc が失敗した場合、終了する必要があることです (メモリを割り当てることができないため)。ただし、終了する前に、使用されているすべてのポインターを解放する必要があります。
問題は、関数が最初に失敗した場合、解放する必要があるポインターが 1 つしかないことです (pStr)。
しかし、2 回目に失敗した場合は、解放する必要がある 2 つのポインター (pstr とユーザー) があります。
どうすれば修正できますか?

4

5 に答える 5

3

何人かが指摘しているように、最近の OS は終了時にメモリを再利用します。ただし、デバッグが容易になるため、とにかくリソースを解放することをお勧めします。たとえば、リークを見つけようとしているときに valgrind などのツールを使用すると、適切に解放されていないすべてのメモリ (プログラム ロジックによるものであっても、これは問題ではありません) がリークとして表示されます。これを行わないことで有名な大規模な API がいくつかあり、それらを使用するアプリケーションでの追跡リークを悪夢にします。

また、一部の特殊な環境では、自分でクリーンアップすることが重要になる場合があります。したがって、今から始めるのは良い習慣です。

ときどき見られるクリーンアップ手法 (Linux カーネルなど) は、私は「ベイル アンド リリース」パターンと考えています。gotoこれは、まだ許容できると考えられている数少ない (おそらく唯一の) コンテキストの 1 つです。それは、リソースを割り当てたのとは逆の順序でリソースを解放できるかどうかにかかっています。通常、これは単一の関数のコンテキストにあります。この場合は次のmain()とおりです。

#include <stdlib.h>

int main(void) {
    int exit_status = EXIT_FAILURE;

    char *s1, *s2, *s3;

    s1 = malloc(100);
    if (!s1) return EXIT_FAILURE;

    s2 = malloc(100);
    if (!s2) goto endB;

    s3 = malloc(100);
    if (!s3) goto endA;

    exit_status = EXIT_SUCCESS;

    /* do whatever */

    free(s3);
endA:
    free(s2);
endB:
    free(s1);
    return exit_status;
}            

説明する: 割り当てs1が失敗した場合は、単に戻ります。クリーンアップするものは何もありません。しかし、割り当てs2に失敗した場合は、 に移動し、 をendB解放しs1ます。割り当てs3に失敗した場合は、 に移動しendA、 と を解放s2s1ます。最後に、すべてが成功した場合は、何をしてもかまいません。その後、3 つのポインターすべてが解放されます。これが通常の関数である場合、ポインターを返す可能性があるため、「終了」ビットの前に別の戻り値があり、代わりに「return null」で完了します。

注意: これを を自由に使用するためのライセンスと見なさないでくださいgoto

于 2012-05-05T17:37:35.267 に答える
3

これは、特定の回答というよりも一般的な C 言語のアドバイスですが、コメントするには長すぎます。

動的リソース管理が存在する場合に C を記述する通常の方法は、goto適切なラベルに続いて関連する割り当て解除呼び出しを行うことです。

int f(int n)
{
    void * p1, * p2, * p3;
    int result = -1;

    p1 = malloc(n);

    if (!p1) goto END0;
    if (n % 2) goto END1;

    p2 = malloc(3 * n);

    if (!p2) goto END1;
    if (n % 7 == 0) goto END2;

    p3 = malloc(7 * n + 8);

    if (!p3) goto END2;

    if (do_useful_stuff(p1, p2, p3) == -1) goto END3;
    result = compute_more_stuff(p1, p2, p3);

END3:
    free(p3);
END2:
    free(p2);
END1:
    free(p1);
END0:
    return result;
}

別の方法は、コードを非常に小さな関数に分割して、一度に 1 つのことだけを実行し、アドホックな方法でリソース割り当てを処理することです。

int small_function(void ** p)
{
    void * q = malloc(13);

    if (some_init(&q) == -1)
    {
        free(q);    // ad-hoc exit management
        return -1;
    }

    int n = complex_computation(q);
    free(q);
    return n;
}
于 2012-05-05T17:40:13.907 に答える
3

ええ、これがプログラム全体であれば、何も解放する必要はありません。

メモリを解放する全体的な理由は、後でプログラムのどこかで再利用できるようにするためです。この時点からプログラムが続行されたとしても、少数のバイトしか割り当てられていません。それらを割り当てて永久に保持しても問題ありません。

実際、正確なサイズの malloc を切り出すためにそこで行っている計算を行う必要さえありません。 256 文字ブロック。待って、あなたの最大値は 8 文字です。または、グローバル バッファを 256 文字または 8 文字の長さにします。次に、strncpy() が len_max を超えないようにしてください。そうしないと、バッファ オーバーフロー ハックの危険があります。

一方、 getinfo() は苦痛に見えます。fgets(mybuffer, len_max, stdin) のようなものを試してください。

最後に確認したところ、実行可能ファイルは解放されていないすべてのブロックを最後に「解放」することすらせず、ただ立ち去ります。VMシステムは、使用されたすべてのメモリ(スタックとプログラムコードを含む)をOSに返し、プロセスは蒸発して終了します. malloc() されたブロックは、そのメモリ上のバイトの単なるパターンであり、すべて忘れられています。

于 2012-05-05T17:44:11.137 に答える
3

すでに述べたように、終了する場合、実用的な最新の O/S はすべて、終了する前に割り当てられたメモリを解放します。いつもそうであったわけではありません。AmigaDOS の初期のバージョンである IIRC は、再起動するまで割り当てられたメモリを自動的に再利用しませんでした。

これは単純なケースです。ファイルをメモリに解析しているときに 579番目のメモリ割り当てが失敗し、ユーザーが再試行できるように以前の 578 番目のメモリ割り当てを解放したいなど、より複雑なケースがあります。

そのような場合、関連する各メモリ割り当ての記録を保持する必要があります (それ自体に何らかのメモリ割り当てが必要になる場合がありますが、ファイルを解析している場合は、完全な説明を含むマスター構造がある可能性があります)。割り当てられたすべてのデータ。

あなたの例では、これがmain()関数ではなく、メモリ割り当てエラーで中止しなかった場合、割り当てられた 3 つのポインターが関数の終了時に解放されるようにする必要があります。そのための標準的なトリックは次のとおりです。

  1. ポインターを確実に解放できるように、ポインターを 0 に初期化します。

    char *host = 0;
    char *user = 0;
    
  2. を使用する場合realloc()、最初のパラメーターとして渡された式に結果を代入しないでください。

    するな:

    ptr = realloc(ptr, newsize);
    

    (when)ptrが割り当てられたメモリへの唯一の参照であり、再割り当てが失敗した場合は、メモリ リークが発生しています。まだ割り当てられているメモリを解放する方法はありません。

    使用する:

    void *newptr = realloc(oldptr, newsize);
    if (newptr == 0)
        ...recover from memory allocation failure
    oldptr = newptr;
    

    単純なバージョンの問題は、割り当てられたメモリへの唯一の参照を破棄したことです。(あなたのコードは危険な/漏れるパターンに陥ることに注意してください)。

  3. リソースを取得するほとんどすべての関数は、取得したリソースを返す前に解放するか、プログラムの他の部分がリソースを使用できるようにして、処理が完了したときに他の部分がリソースを解放できるようにする必要があることに注意してください。

    「利用可能にする」操作は、取得したリソース (メモリと考えてください。ただし、ファイル記述子、ディレクトリ ストリーム、またはその他の多数の割り当てられたリソースのいずれかである可能性があります) を呼び出し元の関数に返すか、またはそれを格納します。現在の関数に渡された構造体、またはそれをグローバル変数または (ファイル) 静的変数にコピーするか、(関数) 静的変数に格納して、関数が再度呼び出された場合に、エントリで使用可能なリソースを確保します。

于 2012-05-05T17:32:40.120 に答える
2

終了する前に、動的に割り当てられたメモリを解放する必要はありません。OS は、そのすべてのメモリを次のプロセスで使用できるようにします。

于 2012-05-05T17:19:06.447 に答える