适当的内存分配?

3

如果不知道函数参数的大小,我该如何仅分配所需的内存?

通常情况下,我会使用固定大小,并通过 sizeof 计算其余部分(注意:代码并不是为了让人理解,而是为了展示问题):

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

int test(const char* format, ...)
{
    char* buffer;
    int bufsize;
    int status;
    va_list arguments;
    va_start(arguments, format);
    bufsize = 1024; /* fixed size */
    bufsize = sizeof(arguments) + sizeof(format) + 1024;
    buffer = (char*)malloc(bufsize);
    status = vsprintf(buffer, format, arguments);
    fputs(buffer, stdout);
    va_end(arguments);
    return status;
}

int main()
{
    const char* name = "World";
    test("Hello, %s\n", name);
    return 0;
}

然而,我认为这不是正确的方法......那么,我应该如何正确地计算所需的缓冲区大小呢?
1个回答

7
如果您可用vsnprintf,我建议使用它。它可以防止缓冲区溢出,因为您提供了缓冲区大小,并返回实际所需大小。
因此,先分配1K缓冲区,然后尝试使用vsnprintf将其写入该缓冲区,限制大小。如果返回的大小小于或等于缓冲区大小,则已成功,您可以直接使用该缓冲区。
如果返回的大小大于缓冲区大小,则调用realloc以获得更大的缓冲区并重试。如果数据没有更改(例如线程问题),则第二个将正常工作,因为您已经知道它有多大。
只要仔细选择默认缓冲区大小,这相对高效。如果绝大多数输出都在该限制内,则很少需要重新分配(请参见下面的可能优化)。
如果您没有类似vsnprintf的函数,我们之前使用的技巧是打开一个文件句柄到/dev/null,并将其用于相同的目的(在输出到缓冲区之前检查大小)。使用vfprintf到该文件句柄以获取大小(输出进入位桶),然后根据返回值分配足够的空间,并vsprintf到该缓冲区。同样,它应该足够大,因为您已经确定了所需大小。
上述方法的优化是对于1K块使用本地缓冲区,而不是分配缓冲区。这避免了在不必要的情况下使用malloc,假设您的堆栈可以处理它。
换句话说,使用类似以下内容:
int test(const char* format, ...)
{
    char buff1k[1024];
    char *buffer = buff1k; // default to local buffer, no malloc.
    :
    int need = 1 + vsnprintf (buffer, sizeof (buff1k), format, arguments);
    if (need > sizeof (buff1k)) {
        buffer = malloc (need);
        // Now you have a big-enough buffer, vsprintf into there.
    }

    // Use string at buffer for whatever you want.
    ...

    // Only free buffer if it was allocated.
    if (buffer != buff1k)
        free (buffer);
}

很好,而且是一个聪明的解决方案。我喜欢它。谢谢 :-) - user350814
1
@nebukadnezzar,我看到你贴的代码有两个问题。1/ 当需要大于初始bufsize时,buffer和mainbuf都不会被释放(将initial_bufsize设置为20来检查此问题 - valgrind可能只会在实际发生泄漏时才捕获它,在您的示例中没有发生)。2/ 您的第二个vsnprintf应该使用need作为大小而不是initial_bufsize。如果您仍然将输出限制为1K,则没有必要分配1.5K。 - paxdiablo
1
你可以通过简单地改变大小参数来修复第二个问题。要修复第一个问题,始终释放 mainbuf,并且只有在 buffer 不等于 mainbuf 时才释放 buffer。另外需要注意的是,我不确定是否可以在两个 v*printf 调用之间没有 va_end/va_start 序列。我模糊地记得这可能是未定义行为,但我不确定。如果它能正常工作,那可能是偶然的结果。 - paxdiablo
1
如果您不想在堆栈上使用1K(例如环境限制),那么没关系,但我认为如果您考虑以这种方式处理,它会简化(并加快)事情的进展。当然,您拥有的解决方案将起作用,但似乎有点浪费,尽管这只是我的意见,请随意忽略一个老家伙的胡言乱语 :-) - paxdiablo
@R,“可能性”取决于malloc引擎和当前场地的状态,但你可能是对的。使用free/malloc对来替代realloc至少是有效的,因为你无论如何都会覆盖它,而且可能更快或更高效。我个人仍然更喜欢基于堆栈的缓冲区作为初始尝试,如果需要额外的空间,则使用malloc,因为这在大多数情况下不需要进行堆操作。 - paxdiablo
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接