3

由于源代码,这个问题有点长,我试图尽可能地简化它。请耐心等待,感谢您的阅读。

我有一个带有可能运行数百万次的循环的应用程序。我想先做一个,然后再做几千到几百万个调用,而不是在那个循环中进行数千到数百万个malloc/调用。freemallocrealloc

但是我遇到了一个问题,当我使用realloc. 如果我使用malloc,我的内存使用情况很好。

valgrind如果我使用's memtest在较小的测试数据集上运行,它会使用malloc或报告没有内存泄漏realloc

我已经验证我将每个malloc-ed(然后是realloc-ed)对象与相应的free.

所以,理论上,我没有泄漏内存,只是使用realloc似乎消耗了我所有可用的 RAM,我想知道为什么以及我能做些什么来解决这个问题。

我最初拥有的是这样的东西,它malloc可以正常使用和工作:

Malloc代码

void A () {
    do {
        B();
    } while (someConditionThatIsTrueForMillionInstances);
}

void B () {
    char *firstString = NULL;
    char *secondString = NULL;
    char *someOtherString;

    /* populate someOtherString with data from stream, for example */

    C((const char *)someOtherString, &firstString, &secondString);

    fprintf(stderr, "first: [%s] | second: [%s]\n", firstString, secondString);

    if (firstString)
        free(firstString);
    if (secondString)
        free(secondString);
}

void C (const char *someOtherString, char **firstString, char **secondString) {
    char firstBuffer[BUFLENGTH];
    char secondBuffer[BUFLENGTH];

    /* populate buffers with some data from tokenizing someOtherString in a special way */

    *firstString = malloc(strlen(firstBuffer)+1);
    strncpy(*firstString, firstBuffer, strlen(firstBuffer)+1);

    *secondString = malloc(strlen(secondBuffer)+1);
    strncpy(*secondString, secondBuffer, strlen(secondBuffer)+1);
}

这工作正常。但我想要更快的东西。

现在我测试一个realloc安排,malloc它只 -s 一次:

重新定位代码

void A () {
    char *firstString = NULL;
    char *secondString = NULL;

    do {
        B(&firstString, &secondString);
    } while (someConditionThatIsTrueForMillionInstances);

    if (firstString)
        free(firstString);
    if (secondString)
        free(secondString);
}

void B (char **firstString, char **secondString) {
    char *someOtherString;

    /* populate someOtherString with data from stream, for example */

    C((const char *)someOtherString, &(*firstString), &(*secondString));

    fprintf(stderr, "first: [%s] | second: [%s]\n", *firstString, *secondString);
}

void C (const char *someOtherString, char **firstString, char **secondString) {
    char firstBuffer[BUFLENGTH];
    char secondBuffer[BUFLENGTH];

    /* populate buffers with some data from tokenizing someOtherString in a special way */

    /* realloc should act as malloc on first pass through */

    *firstString = realloc(*firstString, strlen(firstBuffer)+1);
    strncpy(*firstString, firstBuffer, strlen(firstBuffer)+1);

    *secondString = realloc(*secondString, strlen(secondBuffer)+1);
    strncpy(*secondString, secondBuffer, strlen(secondBuffer)+1);
}

如果我在使用导致百万循环条件的大型数据集free -m运行基于此的测试时在命令行上查看输出realloc,我的内存从 4 GB 下降到 0 并且应用程序崩溃。

我错过了什么realloc导致这种情况?对不起,如果这是一个愚蠢的问题,并提前感谢您的建议。

4

3 回答 3

8

realloc如果无法进行大小调整操作,则必须将内容从旧缓冲区复制到新缓冲区。如果您不需要保留原始内存,A malloc/ freepair 可能比 a 更好。realloc

这就是为什么realloc可能暂时需要比malloc/free对更多的内存。您还通过不断交错reallocs 来鼓励碎片化。即,您基本上是在做:

malloc(A);
malloc(B);

while (...)
{
    malloc(A_temp);
    free(A);
    A= A_temp;
    malloc(B_temp);
    free(B);
    B= B_temp;
}

而原始代码确实:

while (...)
{
    malloc(A);
    malloc(B);
    free(A);
    free(B);
}

在第二个循环结束时,您已经清理了所有使用的内存;与在不完全释放所有内存分配的情况下交错内存分配相比,这更有可能将全局内存堆返回到干净状态。

于 2010-11-15T23:57:15.170 回答
1

realloc当您不想保留内存块的现有内容时使用是一个非常非常糟糕的主意。如果不出意外,您将浪费大量时间来复制您将要覆盖的数据。实际上,按照您使用它的方式,调整大小的块将不适合旧空间,因此它们位于堆上越来越高的地址,导致堆变得荒谬。

内存管理并不容易。糟糕的分配策略会导致碎片化、糟糕的性能等。你能做的最好的事情就是避免引入比你绝对必须做的更多的约束(比如realloc在不需要时使用),当你用完它时尽可能多地释放内存,并在单个分配中而不是小块中一起分配大块相关数据。

于 2010-11-16T03:45:09.273 回答
0

您期望&(*firstString)与 相同firstString,但实际上它是将参数的地址传递给您的函数,而不是传递A. 因此,每次调用时,您都会复制NULL,重新分配新内存,丢失指向新内存的指针,然后重复。您可以通过查看原始指针的末尾A仍然为空来轻松验证这一点。

编辑:嗯,这是一个很棒的理论,但我似乎对我可以测试的编译器有误。

于 2010-11-15T23:58:49.973 回答