3

我是学生,我正在用 C 编写 HTTP 代理应用程序。我在内存管理方面遇到了麻烦。在我之前的所有应用程序中,我只是简单地围绕 malloc 编写了一个包装器,当 malloc 失败时该包装器中止。

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

    assert(size);

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

    return ptr;
}

我现在发现这还不够,因为我只想在由于临时内存不足而导致内存分配失败时拒绝客户端并继续为其他客户端提供服务。如果我不想在每次 malloc 调用后用检查来弄乱我的代码(我在解析代码时每个函数都有很多检查),还有哪些其他选项可以处理内存管理,哪一个最适合我的目的,以及如何是服务器应用程序处理内存管理和内存不足的常用方法吗?

从我当前的代码中考虑这个函数,它从 HTTP 消息的标题部分解析一行(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;
}

我想保持简单并在一个地方处理内存分配错误,并且不要用 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

如果你在一个支持 fork() 的系统上,linux 支持,你可以在它自己的进程中运行每个客户端连接。首次建立客户端连接时,您将主进程分叉为子进程以处理其余的请求。然后你可以像往常一样 abort() 并且只有特定的客户端连接受到影响。这是一个经典的 Unix 服务器模型。

如果您不想或不能使用 fork(),则需要通过抛出异常来中止请求。在 C 中,这将通过在首次建立连接时使用 setjump() 来完成,然后在检测到内存不足时调用 longjump()。这将重置执行并将堆栈返回到调用 setjump() 的位置。

问题是,这将泄漏到该点为止分配的所有资源(例如,其他已成功分配到内存不足的内存分配)。因此,此外,您的内存分配器必须跟踪每个请求的所有内存分配。当调用 longjump() 时,setjump() 返回位置必须释放与中止请求相关的所有内存。

这就是 apache 使用池所做的事情。Apache 使用池来跟踪资源分配,因此它可以在中止或因为代码没有释放它的情况下自动释放它们:http: //www.apachetutor.org/dev/pools

您还应该考虑池模型,而不仅仅是简单地包装 malloc() 以便一个客户端不能用完系统中的所有内存。

于 2013-02-16T23:43:43.197 回答
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 回答
0

另一种可能性是通过使用Boehm 的 GCGC_malloc代替malloc(你不需要调用freeor GC_free);它的 GC_oom_fn函数指针(GC_malloc当没有可用内存时在内部调用)可以设置为您特定的内存不足处理程序(这将拒绝传入的 HTTP 请求,可能带有 a longjmp

使用 Boehm GC 的主要优点是您不再关心free动态分配的数据(假设它是使用GC_mallocor 朋友分配的,例如GC_malloc_atomic对于内部没有任何指针的数据)。

请注意,内存管理不是模块化属性。某些给定数据的活跃度是整个程序的属性,请参阅垃圾收集维基页面和RAII编程习惯。

于 2013-02-17T07:47:39.257 回答