c -为什么我在realloc()中会出现double free错误?

Translate

我试图用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");
    }
}
This question and all comments follow the "Attribution Required."

所有的回答

Translate

作为一般规则,您应该决不在用户提供的缓冲区上进行释放或重新分配。您不知道用户在哪里分配了空间(在您的模块中,在另一个DLL中),因此您不能在用户缓冲区上使用任何分配功能。

假设您现在无法在函数内进行任何重新分配,则应稍微改变其行为,例如仅进行一次替换,因此用户将能够计算结果字符串的最大长度,并为此提供足够长的缓冲区更换发生。

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

导致:

void  strrep(char *input, char *search, char *replace);
char* strrepm(char *input, char *search, char *replace);
void  strrepmfree(char *input);
来源
Translate

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

正如已经指出的那样,当调用realloc()时,您可能会更改指向要重新分配的内存的指针。发生这种情况时,参数“字符串”变为无效。即使您重新分配它,该更改也会在函数结束后超出范围。

为了回答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指出的,你们不能更改作为此函数的输入传递的指针的值。您可以分配所需的任何内容,但是更改将在函数末尾超出范围。在以下块中,函数完成后,“输入”可能是无效指针,也可能不是无效指针:

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()一个新缓冲区,将其返回,然后让调用者决定是否释放旧文本。这还有让调用者保留原始字符串的额外好处!

来源
Translate

只是在黑暗中打了一下,因为我还没有尝试过,但是当您重新分配它时,它返回的指针非常类似于malloc。因为realloc可以根据需要移动指针,所以如果您不执行以下操作,则很可能对无效指针进行操作:

input = realloc(input, strlen(input) + delta);
来源
Translate

有人为两个半月前迟到聚会而道歉。哦,我花了很多时间进行软件考古。

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

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

有什么问题?好吧,您将一个缓冲区传递给realloc(),并且realloc()返回一个指向您应使用的区域的新指针-而您忽略了该返回值。因此,realloc()可能已经释放了原始内存,然后再次向其传递了相同的指针,并且它抱怨您要释放相同的内存两次,因为您再次将原始值传递给了它。这不仅会泄漏内存,而且意味着您将继续使用原始空间-John Downey在黑暗中的镜头表明您滥用了realloc(),但并未强调这样做的严重程度。由于您没有为终止字符串的NUL'\ 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”一书。

来源
Translate

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

好吧,尽管自从我使用C / C ++以来已经有一段时间了,但是增长的realloc仅在原始块之后的内存中有空间时才重用内存指针值。

例如,考虑一下:

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

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

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

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

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

在您的情况下,输入是罪魁祸首。

但是,我会提出另一个建议。就您而言,它看起来像输入变量确实是输入,如果是,则根本不应该对其进行修改。

因此,我将尝试寻找另一种方法来做您想做的事情,而无需更改输入,因为这样的副作用很难追踪。

来源
Translate

这似乎可行;

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;
}

叹气,有没有反汇编的代码?

来源
Translate

realloc很奇怪,很复杂,只应在每秒处理大量内存的情况下使用。即-它实际上使您的代码更快。

我在哪里看到了代码

realloc(bytes, smallerSize);

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

始终使用realloc的返回值。

来源
Translate

我的快速提示。

代替:
void strrep(char *input, char *search, char *replace)
尝试:
void strrep(char *&input, char *search, char *replace)

比体内:
input = realloc(input, strlen(input) + delta);

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

来源