代码之家  ›  专栏  ›  技术社区  ›  Matthew Schinckel

为什么用realloc()得到一个双自由错误?

  •  12
  • Matthew Schinckel  · 技术社区  · 16 年前

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

    如果搜索和替换字符串的长度相同(或者替换字符串比搜索字符串短),这是很简单的,因为我已经分配了足够的空间。如果我尝试使用 realloc() ,我得到一个错误,告诉我我正在做一个双自由-我不知道我是怎样的,因为我只使用 RealLoad() .

    也许一点代码可以帮助:

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

    这个程序可以运行,直到我尝试 RealLoad() 在替换字符串将比初始字符串长的实例中。(这仍然是可行的,它只会吐出错误和结果)。

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

    #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");
        }
    }
    
    8 回复  |  直到 8 年前
        1
  •  12
  •   Adam Tyszecki Ed.    8 年前

    一般来说,你应该 从未 对用户提供的缓冲区执行释放或重新分配。您不知道用户在哪里分配了空间(在您的模块中,在另一个DLL中),因此您不能在用户缓冲区上使用任何分配函数。

    如果您现在不能在函数中进行任何重新分配,那么您应该稍微改变它的行为,就像只做一个替换一样,这样用户就能够计算出结果字符串的最大长度,并为您提供足够长的缓冲区,使这一替换能够发生。

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

    导致:

    void  strrep(char *input, char *search, char *replace);
    char* strrepm(char *input, char *search, char *replace);
    void  strrepmfree(char *input);
    
        2
  •  12
  •   Markus Safar    9 年前

    首先,抱歉我来晚了。这是我的第一个stackoverflow答案。:)

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

    要回答操作,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()一个新的缓冲区,返回它,让调用者决定是否释放旧的文本。这样做还有一个好处,就是让调用者保留原始字符串!

        3
  •  6
  •   John Downey    16 年前

    只是黑暗中的一个镜头,因为我还没试过,但当你重新调整它时,它会像malloc一样返回指针。因为realloc可以在需要时移动指针,所以如果不执行以下操作,则很可能在无效指针上操作:

    input = realloc(input, strlen(input) + delta);
    
        4
  •  6
  •   Markus Safar    9 年前

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

    我感兴趣的是,没有人对原始设计中的内存泄漏进行过明确的评论,或者一次又一次的错误。它观察到了内存泄漏,这就告诉了我你为什么会得到双重自由错误(确切地说,你是多次释放同一个内存——而你是在践踏已经释放的内存之后才这样做的)。

    在进行分析之前,我同意那些说您的接口不够优秀的人;但是,如果您处理了内存泄漏/践踏问题并记录了“必须分配内存”的要求,那么这可能是“好的”。

    有什么问题?好吧,您将一个缓冲区传递给realloc(),realloc()返回一个指向您应该使用的区域的新指针,而忽略该返回值。因此,realloc()可能释放了原始内存,然后您再次将同一指针传递给它,它会抱怨您释放了同一内存两次,因为您再次将原始值传递给它。这不仅会泄露内存,而且意味着你将继续使用原始空间——约翰·唐尼的《黑暗中的枪击》指出你误用了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的《编写可靠代码》一书。

        5
  •  4
  •   Lasse V. Karlsen    16 年前

    注意,尝试编辑代码以摆脱HTML转义代码。

    虽然我使用C/C++已经有一段时间了,但是如果在原来的块内存在内存空间的话,只会重复使用内存指针值。

    例如,考虑一下:

    (XXXXXXXXX………)

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

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

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

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

    在您的案例中,输入是罪魁祸首。

    但是,我会提出另一个建议。在你的情况下,它看起来像 输入 变量确实是输入的,如果是,则根本不应该修改它。

    因此,我会设法找到另一种方法去做你想做的事,而不改变 输入 因为这样的副作用很难找到。

        6
  •  3
  •   Simucal    16 年前

    这似乎奏效了;

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

    叹息,有没有什么办法可以在不吃奶的情况下发布代码?

        7
  •  3
  •   Tom Andersen    13 年前

    realloc是奇怪的、复杂的,应该只在每秒处理大量内存时使用。也就是说,它实际上使代码更快。

    我在哪里见过密码

    realloc(bytes, smallerSize);
    

    用于调整缓冲区的大小,使其变小。工作了大约一百万次,然后出于某种原因realloc决定,即使您正在缩短缓冲区,它也会给您一个很好的新副本。所以你在坏事情发生后1/2秒就在一个随机的地方撞车了。

    始终使用realloc的返回值。

        8
  •  0
  •   Piotr Tyburski    16 年前

    我的提示。

    而不是:
    void strrep(char *input, char *search, char *replace)
    尝试:
    void strrep(char *&input, char *search, char *replace)

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

    通常了解如何将函数参数作为值/引用和realloc()描述来传递。