【问题标题】:String trimming causes memory leak?字符串修剪会导致内存泄漏?
【发布时间】:2010-09-20 23:18:42
【问题描述】:

我很好奇修剪字符串的正确方法是确保不会发生内存泄漏。我想这可能真的是一个基于 free() 工作原理的问题。我已经包含了我的 trim() 函数的代码。见下文。

int main()
{
    char* testStr1 = strdup("some string");
    char* testStr2 = strdup("   some string");
    char* testStr3 = strdup("some string     ");

    trim(&testStr1);
    trim(&testStr2);
    trim(&testStr3);

    free(testStr1); // no memory leak
    free(testStr2); // possible memory leak?
    free(testStr3); // possible memory leak?

    return 0;
}

int trim(char** pStr)
{
 if(pStr == NULL || *pStr == NULL)
  return FAILURE;
 char* str = *pStr;
 while(isspace(*str)) {
  (*pStr)++;
  str++;
 }

 if(*str == 0) {
  *pStr = str;
  return SUCCESS;
 }

 char *end = str + strlen(str) - 1;
 while(end > str && isspace(*end))
  end--;
 *(end+1) = 0;

 *pStr = str;
 return SUCCESS;
}

【问题讨论】:

    标签: c string memory-leaks trim


    【解决方案1】:

    您传递给free 的指针需要完全与您从malloc(或calloccallocrealloc)收到的指针相同,而不仅仅是指向区域的指针malloc 返回的内存。因此,您的第二个字符串是导致问题的字符串。您的第一个和第三个很好,因为您传递给 free 的指针与您从 malloc 收到的指针匹配(通过 strdup)。

    然而,在这种情况下,你得到的并不是真正的内存泄漏——它是未定义的行为。

    【讨论】:

    【解决方案2】:

    是的,这会导致内存泄漏,但更糟糕的是,它会导致未定义的行为。由于trim 修改了指针变量,main 将一个指向free 的指针传递给malloc 没有返回。这是未定义的行为,它会破坏许多实现的堆。

    至少有三种正确的方法来处理这个问题。

    1。让 trim 分配并返回一个新字符串,并让调用者负责释放新字符串以及旧字符串(如果需要):

    char *trim(char *orig);
    // ...
    char *trimmed1 = trim(testStr1);
    free(testStr1);
    // ...
    free(trimmed1);
    

    2。让调用者分配一个长度相同的新字符串(保守一点),并传入两个指针。

    int trim(char *orig, char *new);
    // ...
    char *trimmed1 = malloc(strlen(testStr1) + 1);
    trim(testStr1, trimmed1);
    free(testStr1);
    // ...
    free(trimmed1);
    

    3。将字符串修剪到位,向左移动:

    | | |t|r|im| | |\0|->
    |t|r|i|m|\0|
    
    int *trim(char *orig);
    trim(testStr1);
    // ...
    free(testStr1);
    

    【讨论】:

    • #3 +1。指定函数为客户端代码处理问题,而不是引入新的杂技。
    • 值得注意的是,memmove() 对于实现选项 #3 很有用。
    【解决方案3】:

    这并不是免费工作原理的真正答案,但我会按照以下思路做一些事情:

    char * trim_realloc(char * str) { 字符 * p = str; 字符 * e; 字符 * ne; // 新的结束 字符 * r; size_t len;

    // Since you put this level of error testing in your program
    if (!str) {
       return str; // str is NULL
    }
    
    while (*p || isspace(*p) ) {
        p++;
    }
    
    len = strlen(p);
    e = p + len;
    
    ne = e;
    
    while (ne > p) {
        if (isspace(*ne)) {
           *ne = 0;
           ne--;
        } else {
            break;
        }
    }
    
    
    if (p == str) {
       if (e != ne) {
           return realloc(str, len+1);  // only tail trim -- you could just return str here
       } else {
           return str; // no actual trim
       }
    } else {
        r = strdup(p);
        free(str); // str is the head of the string, so that's what we have to free
        return r;
    }
    

    }

    您应该注意我对realloc 行的评论因为无论如何我都将尾随空间归零(并且由于许多 realloc 实现只担心“它是否足够大”,而不是“是否有太多额外空间”)你可以最后让你的字符串所在的缓冲区占用了太多空间。它仍然 \0 在正确的位置终止(除非我的未经测试的代码中存在错误,这可能存在)。

    您可以做的其他事情是将字符串移动到缓冲区的开头,然后修剪尾部,这样:

    "  cat   "
    

    经历了这些步骤:

    “c猫” “卡猫” “猫猫” “猫在” “猫猫” “猫”

    在你开始修剪尾巴之前。

    现在,回到 free 的工作原理—— free 需要传递 NULL 或堆分配函数之一传递给您的值。实现了一些堆分配库,以便当 malloc 分配数据时,该数据块的大小存储在 malloc 返回地址之前的字节中,当您调用 free 时,该指针前面的字节用于确定该内存块的大小实际上是。如果您传递了 malloc (或 calloc、或 realloc 或类似)未返回的内容,则 free 可能会在错误的位置查找并使用它在那里找到的任何内容作为您正在释放的块的大小 - 没有任何好处这个。

    【讨论】:

      【解决方案4】:

      您不需要额外的 malloc/realloc/... 修剪,例如:

      char *trim(char *s)
      {
        while( isspace(*s) )
          memmove( s, s+1, strlen(s) );
        while( *s && isspace(s[strlen(s)-1]) )
          s[strlen(s)-1] = 0;
        return s;
      }
      

      速度不快但安全,对于您的示例,免费永远不会失败,因为 s 没有改变。只有 内容 可以改变。

      【讨论】:

        猜你喜欢
        • 2011-06-12
        • 2017-05-09
        • 2011-11-09
        • 1970-01-01
        • 2021-05-01
        • 2012-06-15
        • 2021-03-23
        • 2021-09-25
        • 2014-12-08
        相关资源
        最近更新 更多