3

私は学生で、CでHTTPプロキシアプリケーションを作成しています。メモリ管理に問題があります。以前のすべてのアプリケーションでは、mallocが失敗したときに中止されるmallocのラッパーを作成しただけです。

void *xmalloc(size_t size)
{
    void *ptr;

    assert(size);

    ptr = malloc(size);
    if (!ptr) 
        abort();

    return ptr;
}

これは、一時的なメモリ不足のためにメモリ割り当てが失敗したときに、クライアントを拒否して他のクライアントにサービスを提供し続けたいだけなので、今では不十分だと感じています。各malloc呼び出しの後にチェックでコードを乱雑にしたくない場合(コードの解析では関数ごとにかなり多くのオプションがあります)、メモリ管理を処理する他のオプションは何ですか?また、どのオプションが私の目的に最適で、どのようにサーバーアプリケーションがメモリ管理とメモリ不足を処理するための一般的な方法は何ですか?

HTTPメッセージのヘッダー部分から1行を解析する現在のコードからこの関数を検討してください(xstrndupはxmallocを呼び出します)。

int http_header_parse(http_hdr_table *t, const char *s)
{
    const char *p;
    const char *b;
    char *tmp_name;
    char *tmp_value;
    int ret = -1;

    assert(t);
    assert(s);

    p = b = s;

    /* field name */
    for (; ; p++) {
        if (*p == ':') {
            if (p-b <= 0) goto out;
            tmp_name = xstrndup(b, p-b);
            b = ++p;
            break;
        }

        if (is_ctl_char(*p) || is_sep_char(*p)) goto out;
    }

    while (*p == ' ' || *p == '\t') {
        p++; b++;
    }

    /* field value */
    for (; ; p++) {
        if (is_crlf(p)) {
            if (p-b <= 0) goto err_value;
            tmp_value = xstrndup(b, p-b);
            p += 2;
            break;
        }

        if (!*p) goto err_value;
    }
    http_hdr_table_set(t, tmp_name, tmp_value);

    ret = 0;
    xfree(tmp_value);
err_value:
    xfree(tmp_name);
out:
    return ret;
}

物事をシンプルに保ち、メモリ割り当てエラーを1か所で処理し、mallocエラー処理コードでコードを乱雑にしないようにしたいと思います。私は何をすべきか?ありがとうございました。

PS:私はPOSIX/Unixライクなシステムで実行するアプリケーションを書いています。また、私の現在のコーディングスタイルと慣習を自由に批判してください。

4

5 に答える 5

3

C のような比較的低レベルの言語を使用する場合はif(tmp_value == NULL) goto out;、2 か所のようなものを追加することについてあまり心配する必要はありません。

些細なコードを 2 行追加するという考えに耐えられない場合は、例外を適切にサポートする言語 (C++ など) を試して、代わりに throw/try/catch を追加してください。注: 私は C++ が本当に好きではありませんが、C++ を使用することは、C で独自の「例外のような」機能と自動化されたリソース割り当て解除のレイヤー全体を実装するよりも理にかなっている必要があります。

于 2013-02-16T23:53:56.373 に答える
1

現代の言語では、ガベージ コレクションと例外が提供されます。Cはそうではないので、一生懸命働かなければなりません。ここには魔法の解決策はありません。

いくつかのヒント:

  1. セッション構造を作成し、そこから割り当てられたすべてのメモリを保持します。セッションが中止された場合は、常にクリーンアップ関数を呼び出します。このように、多くの場所で障害をチェックする必要がある場合でも、少なくともすべての障害は同じ方法で処理されます。
  2. session_allocate()メモリを割り当て、セッション構造からポイントされたリンク リストにメモリを保持する関数を作成することもできます。この関数を使用して割り当てたものはすべて、セッションが破棄されると解放されます。
  3. セッションの開始時にすべての割り当てを集中するようにしてください。必要なものをすべて割り当てた後は、コードの残りの部分で障害を心配する必要はありません。
于 2013-02-17T07:59:31.733 に答える
1

Linux がサポートする fork() をサポートするシステムを使用している場合は、各クライアント接続を独自のプロセスで実行できます。クライアント接続が最初に確立されると、メイン プロセスを子プロセスにフォークして、残りのリクエストを処理します。次に、いつものように abort() を実行すると、特定のクライアント接続のみが影響を受けます。これは古典的な UNIX サーバー モデルです。

fork() を使用したくない、または使用できない場合は、例外をスローしてリクエストを中止する必要があります。C では、接続が最初に確立されたときに setjump() を使用し、メモリ不足が検出されたときに longjump() を呼び出すことによってこれを行います。これにより、実行がリセットされ、スタックが setjump() が呼び出された場所に戻ります。

問題は、これにより、その時点までに割り当てられたすべてのリソースがリークすることです (たとえば、メモリ不足になるまでに成功した他のメモリ割り当て)。さらに、メモリ アロケータは、各リクエストのすべてのメモリ割り当てを追跡する必要があります。longjump() が呼び出されると、setjump() の戻り位置は、中止された要求に関連付けられていたすべてのメモリを解放する必要があります。

これは、Apache がプールを使用して行うことです。Apache はプールを使用してリソース割り当てを追跡し、アボートの場合やコードが解放しなかった場合に自動的に解放できるようにします: http://www.apachetutor.org/dev/pools

また、単純に malloc() をラップするだけでなく、プール モデルも考慮する必要があります。これにより、1 つのクライアントがシステム内のすべてのメモリを使い果たすことができなくなります。

于 2013-02-16T23:43:43.197 に答える
0

もう 1 つの可能性は、代わりにitsを使用してBoehm の GCを使用することです ( orを呼び出す必要はありません)。その 関数ポインタ (メモリが利用できなくなったときに内部的に呼び出される) を特定のメモリ不足ハンドラーに設定できます (これは、おそらく を使用して、着信 HTTP 要求を拒否します) 。GC_mallocmallocfreeGC_freeGC_oom_fnGC_malloclongjmp

freeBoehm GC を使用する主な利点は、動的に割り当てられたデータを -ing することを気にしなくてもよいことです(ただしGC_malloc、または友人を使用して割り当てられた場合、たとえばGC_malloc_atomic内部にポインターを持たないデータの場合)。

メモリ管理はモジュール プロパティではないことに注意してください。特定のデータの活性は、プログラム全体のプロパティです。ガベージ コレクションのウィキページとRAIIプログラミング イディオムを参照してください。

于 2013-02-17T07:47:39.257 に答える
0

もちろん を使用することもできますがalloca、注意して使用する必要があるという問題があります。または、コードを記述して、malloc の使用を最小限に抑えてローカライズすることもできます。たとえば、上記の関数を書き直して割り当てをローカライズできます。

static size_t field_name_length(const char *s)
{
    const char *p = s;
    for ( ; *p != ':'; ++p) {
        if (is_ctl_char(*p) || is_sep_char(*p))
            return 0;
    }
    return (size_t) (p - s);
}

static size_t value_length(const char *s)
{
    const char *p = s;
    for (; *p && !is_crlf(p); p+=2) {
        /* nothing */
    }
    return *p ? (size_t) (p - s) : 0;
}

int http_header_parse(http_hdr_table *t, const char *s)
{
    const char *v;
    int ret = -1;
    size_t v_len = 0;
    size_t f_len = field_name_length(s);

    if (f_len) {
        v = s + f_len + 1;
        v = s + strspn(s, " \t");
        v_len = value_length(s);
    }
    if (v_len > 0 && f_len > 0) {
        /* Allocation is localised to this block */
        const char *name = xstrndup(s, f_len);
        const char *value = xstrndup(v, v_len);

        if (name && value) {
            http_hdr_table_set(t, name, value);
            ret = 0;
        }
        xfree(value);
        xfree(name);
    }
    return ret;
}

または、さらに良いことhttp_hdr_table_setに、ポインターと長さを受け入れるように変更して、割り当てを完全に回避することもできます。

于 2013-02-26T23:39:07.430 に答える