【问题标题】:Why am I getting a double free or corruption error with realloc()?为什么使用 realloc() 时会出现双重释放或损坏错误?
【发布时间】:2010-09-05 07:04:07
【问题描述】:

我尝试在 C 中编写一个字符串替换函数,该函数适用于使用 malloc() 分配的 char *。它有点不同,它会查找和替换字符串,而不是起始字符串中的字符。

如果搜索和替换字符串的长度相同(或者替换字符串比搜索字符串短),这很简单,因为我分配了足够的空间。如果我尝试使用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 malloc


    【解决方案1】:

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

    假设你现在不能在你的函数中进行任何重新分配,你应该稍微改变它的行为,比如只做一次替换,这样用户就可以计算得到的字符串最大长度,并为你提供足够长的缓冲区以便进行这一替换。

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

    导致:

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

    【讨论】:

      【解决方案2】:

      首先,抱歉我迟到了。这是我的第一个 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 所指出的,你们不能更改作为该函数输入的指针的值。您可以分配您想要的任何内容,但更改将在函数结束时超出范围。在以下块中,一旦函数完成,“输入”可能是也可能不是无效指针:

      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】:

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

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

        【讨论】:

        • 如果 realloc 失败,它返回 NULL,并且不理会现有的缓冲区。您刚刚丢失了指针... :-(
        【解决方案4】:

        其他人为迟到而道歉 - 两个半月前。哦,好吧,我花了很多时间做软件考古。

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

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

        有什么问题?好吧,你将一个缓冲区传递给 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”一书。

        【讨论】:

        • 谢谢,这是对我做错了什么的一个很好的分析(从某种意义上说,双重释放是我做错的几件事的副产品。)我想我在我的脑海中,realloc() 只是扩展了内存分配 - 当我想到它时,这根本没有意义!
        【解决方案5】:

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

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

        例如,考虑一下:

        (xxxxxxxxxx.......)

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

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

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

        然而,这个指针值已经被释放了。

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

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

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

        【讨论】:

          【解决方案6】:

          这似乎有效;

          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】:

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

          我看过代码在哪里

          realloc(bytes, smallerSize);
          

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

          始终使用 realloc 的返回值。

          【讨论】:

            【解决方案8】:

            我的快速提示。

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

            比在正文中:
            input = realloc(input, strlen(input) + delta);

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

            【讨论】:

            • 符号 void strrep(char *&amp;input, char *search, char *replace) 在 C 中无效——尽管它在 C++ 中有效。问题不是,而且 AFAICT 从来没有被 C++ 标记。最好的代码应该是void strrep(char **input, char *search, char *replace),尽管很容易争论char *strrep(const char *input, const char *search, const char *replace) 是一个可行的接口(输入字符串没有改变;修改后的字符串被分配并返回)。
            猜你喜欢
            • 2021-12-04
            • 2021-12-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-03-12
            • 1970-01-01
            • 2013-12-04
            相关资源
            最近更新 更多