为什么我在使用 realloc() 时出现双重释放或损坏错误?

Why am I getting a double free or corruption error with realloc()?

提问人:Matthew Schinckel 提问时间:8/4/2008 最后编辑:Matthew Schinckel 更新时间:1/24/2020 访问量:8999

问:

我尝试用 C 编写一个字符串替换函数,该函数适用于 ,该函数已使用 .它略有不同,因为它将查找并替换字符串,而不是起始字符串中的字符。char *malloc()

如果搜索和替换字符串的长度相同(或替换字符串比搜索字符串短),则这样做是微不足道的,因为我分配了足够的空间。如果我尝试使用 ,我会收到一个错误,告诉我我正在做一个双重释放 - 我不明白我是怎么回事,因为我只使用 .realloc()realloc()

也许一点代码会有所帮助:

void strrep(char *input, char *search, char *replace) {
    int searchLen = strlen(search);
    int replaceLen = strlen(replace);
    int delta = replaceLen - searchLen;
    char *find = input;

    while (find = strstr(find, search)) {

        if (delta > 0) {
            realloc(input, strlen(input) + delta);
            find = strstr(input, search);            
        }

        memmove(find + replaceLen, find + searchLen, strlen(input) - (find - input));
        memmove(find, replace, replaceLen);
    }
}

程序可以工作,直到我尝试在替换的字符串将比初始字符串长的情况下工作。(它仍然有效,它只是吐出错误和结果)。realloc()

如果有帮助,调用代码如下所示:

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

void strrep(char *input, char *search, char *replace);

int main(void) {
    char *input = malloc(81);

    while ((fgets(input, 81, stdin)) != NULL) {
        strrep(input, "Noel", "Christmas");
    }
}
c 马洛克

评论


答:

6赞 John Downey 8/4/2008 #1

只是在黑暗中拍摄,因为我还没有尝试过,但是当您重新定位时,它会像 malloc 一样返回指针。由于 realloc 可以在需要时移动指针,因此,如果不执行以下操作,则很可能正在对无效指针进行操作:

input = realloc(input, strlen(input) + delta);

评论

0赞 Roger Lipscombe 7/9/2009
如果 realloc 失败,它将返回 NULL,并保留现有缓冲区。你刚刚丢失了指针... :-(
4赞 Lasse V. Karlsen 8/4/2008 #2

请注意,尝试编辑您的代码以摆脱 html 转义代码。

好吧,虽然我已经有一段时间没有使用 C/C++ 了,但增长的 realloc 只有在原始块之后有内存空间时才会重用内存指针值。

例如,考虑一下:

(xxxxxxxxxx..........)

如果指针指向第一个 x,并且 .表示可用内存位置,并且您将变量指向的内存大小增加 5 个字节,它将成功。这当然是一个简化的示例,因为块被四舍五入到一定大小以进行对齐,但无论如何。

但是,如果您随后尝试将其再增加 10 个字节,并且只有 5 个字节可用,则需要在内存中移动块并更新指针。

但是,在您的示例中,您向函数传递的是指向字符的指针,而不是指向变量的指针,因此,虽然 strrep 函数内部可能能够调整正在使用的变量,但它是 strrep 函数的局部变量,您的调用代码将保留原始指针变量值。

但是,此指针值已被释放。

就你而言,输入是罪魁祸首。

但是,我会提出另一个建议。在您的例子中,输入变量看起来确实是输入变量,如果是,则根本不应该修改它。

因此,我会尝试找到另一种方法来做你想做的事情,而不改变输入,因为像这样的副作用可能很难追踪。

15赞 Vincent Robert 8/4/2008 #3

作为一般规则,您永远不应该在用户提供的缓冲区上执行 free 或 realloc。你不知道用户在哪里分配了空间(在你的模块中,在另一个 DLL 中),因此你不能在用户缓冲区上使用任何分配函数。

如果您现在无法在函数中进行任何重新分配,则应稍微更改其行为,例如仅执行一次替换,以便用户能够计算生成的字符串最大长度,并为您提供足够长的缓冲区,以便进行一次替换。

然后,您可以创建另一个函数来执行多个替换,但您必须为生成的字符串分配整个空间并复制用户输入字符串。然后,您必须提供一种方法来删除您分配的字符串。

导致:

void  strrep(char *input, char *search, char *replace);
char* strrepm(char *input, char *search, char *replace);
void  strrepmfree(char *input);
0赞 Piotr Tyburski 8/4/2008 #4

我的快速提示。

而不是:尝试:

void strrep(char *input, char *search, char *replace)
void strrep(char *&input, char *search, char *replace)

比在身体里:
input = realloc(input, strlen(input) + delta);

通常阅读有关将函数参数作为 values/reference 传递和 realloc() 描述:)的信息。

评论

0赞 Jonathan Leffler 5/28/2016
该符号在 C 中无效——尽管它在 C++ 中有效。问题不是,AFAICT也从来没有被标记为C++。在最好的情况下,代码应该是 ,尽管很容易争辩说这是一个可行的接口(输入字符串不会更改;修改后的字符串会被分配并返回)。void strrep(char *&input, char *search, char *replace)void strrep(char **input, char *search, char *replace)char *strrep(const char *input, const char *search, const char *replace)
3赞 Mark 8/4/2008 #5

这似乎有效;

char *strrep(char *string, const char *search, const char *replace) {
    char *p = strstr(string, search);

    if (p) {
        int occurrence = p - string;
        int stringlength = strlen(string);
        int searchlength = strlen(search);
        int replacelength = strlen(replace);

        if (replacelength > searchlength) {
            string = (char *) realloc(string, strlen(string) 
                + replacelength - searchlength + 1);
        }

        if (replacelength != searchlength) {
            memmove(string + occurrence + replacelength, 
                        string + occurrence + searchlength, 
                        stringlength - occurrence - searchlength + 1);
        }

        strncpy(string + occurrence, replace, replacelength);
    }

    return string;
}

唉,无论如何都可以在不吸吮的情况下发布代码吗?

评论

0赞 Matthew Schinckel 8/14/2015
添加评论,因为评论是作为答案编写的,在评论可用之前:这似乎只改变了第一次出现的情况。这可能是合理的,因为我并没有真正说它必须改变所有这些!
13赞 Tryke 8/9/2008 #6

首先,对不起,我来晚了。这是我的第一个stackoverflow答案。:)

如前所述,当调用 realloc() 时,您可能会更改指向正在重新分配的内存的指针。发生这种情况时,参数“string”将失效。即使重新分配它,一旦函数结束,更改也会超出范围。

为了应答 OP,realloc() 返回一个指向新重新分配的内存的指针。返回值需要存储在某个位置。通常,您会这样做:

data *foo = malloc(SIZE * sizeof(data));
data *bar = realloc(foo, NEWSIZE * sizeof(data));

/* Test bar for safety before blowing away foo */
if (bar != NULL)
{
   foo = bar;
   bar = NULL;
}
else
{
   fprintf(stderr, "Crap. Memory error.\n");
   free(foo);
   exit(-1);
}

正如 TyBoer 所指出的,你们不能更改作为此函数输入传入的指针的值。您可以分配所需的任何内容,但更改将在函数结束时超出范围。在以下块中,一旦函数完成,“input”可能是也可能不是无效指针:

void foobar(char *input, int newlength)
{
   /* Here, I ignore my own advice to save space. Check your return values! */
   input = realloc(input, newlength * sizeof(char));
}

Mark 尝试通过将新指针作为函数的输出返回来解决此问题。如果这样做,则调用方有责任不再使用他用于输入的指针。如果它与返回值匹配,则您有两个指向同一位置的指针,并且只需要在其中一个上调用 free()。如果它们不匹配,则输入指针现在指向进程可能拥有也可能不拥有的内存。取消引用它可能会导致分段错误。

您可以对输入使用双指针,如下所示:

void foobar(char **input, int newlength)
{
   *input = realloc(*input, newlength * sizeof(char));
}

如果调用方在某处有输入指针的副本,则该副本现在可能仍然无效。

我认为这里最干净的解决方案是在尝试修改函数调用者的输入时避免使用 realloc()。只需 malloc() 一个新的缓冲区,返回它,然后让调用者决定是否释放旧文本。这还有一个额外的好处,就是让调用方保留原始字符串!

6赞 Jonathan Leffler 10/21/2008 #7

还有人为晚到聚会道歉——两个半月前。哦,好吧,我花了很多时间做软件考古学。

我感兴趣的是,没有人明确评论原始设计中的内存泄漏或差一错误。正是通过观察内存泄漏,才确切地告诉我为什么会出现双重释放错误(因为,准确地说,您多次释放相同的内存 - 而且是在践踏已经释放的内存之后这样做的)。

在进行分析之前,我同意那些说你的界面不那么出色的人;但是,如果您处理了内存泄漏/践踏问题并记录了“必须分配内存”要求,则可能是“正常”。

存在哪些问题?好吧,你把一个缓冲区传递给 realloc(),realloc() 会返回一个指向你应该使用的区域的新指针 - 你忽略了这个返回值。因此,realloc() 可能已经释放了原始内存,然后您再次向它传递相同的指针,它抱怨您释放了相同的内存两次,因为您再次将原始值传递给它。这不仅会泄露内存,还意味着你继续使用原始空间——约翰·唐尼(John Downey)在黑暗中的镜头指出你滥用了realloc(),但没有强调你这样做的严重性。还有一个差一错误,因为您没有为终止字符串的 NULL“\0”分配足够的空间。

发生内存泄漏的原因是,您没有提供一种机制来告知调用方字符串的最后一个值。因为你一直在践踏原始字符串加上它后面的空格,所以看起来代码是有效的,但如果你的调用代码释放了空间,它也会得到一个双重释放错误,或者它可能会得到一个核心转储或等效的,因为内存控制信息被完全打乱了。

您的代码也无法防止无限增长 - 请考虑将“Noel”替换为“Joyeux Noel”。每次,你都会添加 7 个字符,但你会在替换的文本中找到另一个 Noel,然后展开它,依此类推。我的修复(如下)没有解决这个问题 - 简单的解决方案可能是检查搜索字符串是否出现在替换字符串中;另一种方法是跳过替换字符串,并在其后继续搜索。第二个问题有一些重要的编码问题需要解决。

因此,我建议对您调用的函数进行修订:

char *strrep(char *input, char *search, char *replace) {
    int searchLen = strlen(search);
    int replaceLen = strlen(replace);
    int delta = replaceLen - searchLen;
    char *find = input;

    while ((find = strstr(find, search)) != 0) {
        if (delta > 0) {
            input = realloc(input, strlen(input) + delta + 1);
            find = strstr(input, search);            
        }

        memmove(find + replaceLen, find + searchLen, strlen(input) + 1 - (find - input));
        memmove(find, replace, replaceLen);
    }

    return(input);
}

此代码不会检测内存分配错误 - 如果 realloc() 失败,则可能会崩溃(但如果没有,则会泄漏内存)。请参阅 Steve Maguire 的《Writing Solid Code》一书,了解对内存管理问题的广泛讨论。

评论

1赞 Matthew Schinckel 11/22/2008
谢谢,这是对我做错了什么的非常好的分析(从某种意义上说,双重释放是我做错的几件事的副产品。我想我脑子里有 realloc() 只是扩展了内存分配 - 当我想到它时,这根本没有意义!
3赞 Tom Andersen 5/17/2011 #8

Realloc 很奇怪,很复杂,只有在每秒处理大量内存时才应该使用。即 - 它实际上使您的代码更快。

我看过代码

realloc(bytes, smallerSize);

用于调整缓冲区的大小,使其更小。工作了大约一百万次,然后出于某种原因,realloc 决定即使您缩短缓冲区,它也会为您提供一个不错的新副本。因此,在坏事发生后 1/2 秒,你会在一个随机的地方坠毁。

始终使用 realloc 的返回值。