【问题标题】:How do I avoid memory leak when concat string pointers in C在C中连接字符串指针时如何避免内存泄漏
【发布时间】:2018-05-25 10:19:35
【问题描述】:

我正在为一个编程语言类开发解析器,并且在将我的表达式树转换为预排序中的字符串时遇到了内存泄漏问题。在我有它之前,在 exprToString 中为字符串分配内存时,它是 100 * sizeof(char),但是内存泄漏结果要大得多。所以,我摆脱了 100 * 但我仍然得到非 ascii 打印输出和奇怪的重复出现的“retu”......

char* concat(const char *s1, const char *s2)
{
    char *result = malloc(strlen(s1)+strlen(s2)+1);//+1 for the null-
    terminator
    //free(result);
    //in real code you would check for errors in malloc here
    strcpy(result, s1);
    strcat(result, s2);
    return result;
}

char* exprToString(Expr* ex){
    char *string = (char*)malloc(sizeof(char));
    //free(string);
    if(strcmp(ex->value,"18killstreak") == 0){
        return " ";
    }
    string = concat(string,ex->value);
    string = concat(string,exprToString(ex->a));
    string = concat(string,exprToString(ex->b));
    return string;
}

这是一个示例表达式:z / ( x + 2 * x ) 这是打印输出:0etur/z +x *2 x

发生了什么事?

【问题讨论】:

  • char *string = (char*)malloc(sizeof(char)); 仅分配 1 个字节...并且非 nul 终止,因此进一步的 strlen 调用返回未定义的值,并且您正在将一个垃圾字符串与另一个添加。
  • 所以我只需要在 concat 之前将 \0 附加到我的所有字符串?
  • 不,那只是第一个错误。我的回答中解释了内存泄漏。
  • 看看 realloc,它应该可以解决您每次返回新缓冲区并造成内存泄漏的问题。

标签: c string pointers memory-leaks concatenation


【解决方案1】:

您的concat 函数没问题,但exprToString 的开头错误:

    char *string = (char*)malloc(sizeof(char));    
    string = concat(string,ex->value);

你只分配了一个 1 字节的字符串,它甚至没有以 nul 结尾,所以进一步调用concat 将一些长度未定义的字符串与ex->value 连接起来。

如果您想要用ex-value 的副本初始化string,只需将上面的替换为:

char *string = strdup(ex->value);

下一个问题是这样做时的内存泄漏:

string = concat(string,exprToString(ex->a));

您正在覆盖 string 的先前值,因此它可以工作,但是您会因为没有机会释放输入而导致内存泄漏。

您可以更改concat,以便通过调用释放第一个参数:

free(s1);

就在返回结果之前。或者在s1 上使用realloc,这样它就会释放然后调整大小。

但两者都不是很方便。如果某个调用者没有阅​​读文档,需要其他字符串,或者如果您将文字作为第一个参数传递,该怎么办?这将使您的 concat 函数不那么有用且容易出错(除非出于特殊目的使用非常特殊的名称调用它)

最简单的方法是分解您的concat 调用:

char *temp = concat(string,ex->value);
free(string);
string = temp;

C 语言在幕后没有太多神奇之处,例如运算符重载或垃圾回收,因此所有内容都必须显式编写。

【讨论】:

    【解决方案2】:

    问题的当前代码中有(至少)两个严重的错误:

    char* exprToString(Expr* ex){
        char *string = (char*)malloc(sizeof(char));
        //free(string);
        if(strcmp(ex->value,"18killstreak") == 0){
            return " ";
        }
        string = concat(string,ex->value);
        string = concat(string,exprToString(ex->a));
        string = concat(string,exprToString(ex->b));
        return string;
    }
    

    首先,也是最微妙的是,您有时(主要)返回指向已分配内存的指针,但有时返回指向常量字符串的指针。这意味着调用代码不能可靠地在返回值上调用free(),这会在返回的指针被释放时导致微妙的(或者可能不是那么微妙的)内存损坏,或者由于返回的指针从未被释放而导致泄漏。两者都不令人满意。确保如果你返回一个指针,它可以被统一释放。考虑返回 NULL 指针而不是单空字符串,但如果必须返回单空字符串,请确保分配它。请注意,提前返回也会泄漏内存,这完全是不必要的。

    其次,更严重的是,您必须保留指向 concat 返回的内存的指针,以便您可以释放除返回给调用者的值之外的所有内容。

    第一个问题在:

    char *string = (char*)malloc(sizeof(char));
    

    你没有检查内存分配是否成功。你不能确保你有一个字符串。您需要*string = '\0'; 才能安全。

    那么你有:

    string = concat(string, ex->value);
    

    在此之后,您丢失了指向由第一个 malloc() 分配的内存的唯一指针——这正是内存泄漏的定义。接下来的两行犯了同样的错误;你在返回之前已经泄露了 3 块内存。

    一个解决方案是:

    char* exprToString(Expr* ex){
        if (strcmp(ex->value, "18killstreak") == 0){
            return 0;
        }
        char *string = (char*)malloc(sizeof(char));
        if (string == 0)
            return 0;
        *string = '\0';
        char *t1 = concat(string, ex->value);
        free(string);
        string = t1;
        t1 = concat(string, exprToString(ex->a));
        free(string);
        string = t1;
        string = concat(string, exprToString(ex->b));
        free(string);
        string = t1;
        return string;
    }
    

    这开始看起来令人不快的重复。另一种解决方案更改concat() — 代码和语义。如果您要求第一个指针是指向可释放(可重新分配)内存的指针,您可以使用:

    char *concat(char *s1, const char *s2)
    {
        if (s1 == 0)
            return 0;
        size_t len_1 = strlen(s1);
        size_t len_2 = strlen(s2);
        char *result = realloc(s1, len_1 + len_2 + 1);
        if (result == 0)
        {
            free(s1);
            return 0;
        }
        strcat(result, s2);
        return result;
    }
    

    现在您可以使用原始代码的略微修改版本:

      char* exprToString(Expr* ex){
        if (strcmp(ex->value, "18killstreak") == 0){
            return 0;
        }
        char *string = (char*)malloc(sizeof(char));
        if (string == 0)
            return 0;
        *string = '\0';
        string = concat(string, ex->value);
        string = concat(string, exprToString(ex->a));
        string = concat(string, exprToString(ex->b));
        return string;
    }
    

    现在只有更多泄漏的选项,因为exprToString() 返回一个指向您永远不会释放的已分配内存的指针。也有多种方法可以解决这个问题。其中之一是重新定义concat() 以获取指向可释放(可重新分配)内存的两个 指针,并在返回之前释放第二个指针。然后,您必须担心电话 string = concat(string, ex->value); — 例如,一个安全的替代方案是 string = concat(string, strdup(ex->value));。 (至少,如果concat() 函数检查空指针是安全的。)

    可能还有其他我尚未发现的问题。建议的代码尚未通过编译器——其中可能存在错误。特别是,我没有完全跟踪每次可能的显式分配失败后会发生什么。错误恢复代码可能存在问题。

    有些人坚决反对casting the result of malloc()。如果您保证使用可确保在使用之前声明 malloc() 的选项进行编译,我并不坚持这一点。我按照原始代码保留了演员表。

    【讨论】:

      【解决方案3】:

      这是内存泄漏的来源:

      char *string = (char*)malloc(sizeof(char));
      ...
      string = concat(string,ex->value);
      string = concat(string,exprToString(ex->a));
      string = concat(string,exprToString(ex->b));
      

      每次调用 concat 时,都会用新的指针值覆盖 string 的当前值,其中包含指向 malloc 内存的指针,并丢弃旧值。

      如果concat 不为NULL,则应将realloc 更改为在第一个参数上使用。这样您就可以根据需要增加缓冲区。在第一次调用concat 时,您需要将第一个参数设置为 NULL。

      所以修改concat使用realloc

      char* concat(const char *s1, const char *s2)
      {
          char *result;
          if (s1) {
              result = realloc(s1, strlen(s1)+strlen(s2)+1);
              if (!result) {
                  perror("realloc failed");
                  free(s1);
                  return NULL;
              }
              strcpy(result, s2);
          } else {
              result = malloc(strlen(s2)+1);
              if (!result) {
                  perror("malloc failed");
                  return NULL;
              }
              strcat(result, s2);
          }
          return result;
      }
      

      然后你把exprToString改成:

      char* exprToString(Expr* ex){
          if(strcmp(ex->value,"18killstreak") == 0){
              return strdup(" ");
          }
          char *string = concat(NULL,ex->value);
          char *a_str = exprToString(ex->a);
          char *b_str = exprToString(ex->b);
          string = concat(string,a_str);
          string = concat(string,b_str);
          free(a_str);
          free(b_str);
          return string;
      }
      

      请注意,由于此函数预计会返回动态分配的内存,因此基本情况不能直接返回字符串文字。调用此函数的人负责释放返回的缓冲区。

      【讨论】:

      • 请注意,如果concat() 将释放它的第二个参数(或realloc() 它的第一个参数),那么它的参数在非NULL 时必须是指向动态分配内存的指针。对于 OP 当前的exprToString() 实现,有时肯定不是。应该在exprToString() 中修复。
      • @JohnBollinger 第二个参数可以是任何字符串。它只是第一个必须指向动态分配的内存。
      • 啊。我看到我读你的代码太快了。那么,我是否误以为您的答案没有解决exprToString()的递归引起的内存泄漏?
      • @JohnBollinger 你是对的,我错过了exprToString 是递归的。这使事情有点复杂。我会更新以解决这个问题。
      猜你喜欢
      • 2020-05-03
      • 1970-01-01
      • 2017-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-11
      • 1970-01-01
      相关资源
      最近更新 更多