Обрезка строки вызывает утечку памяти? - PullRequest
1 голос
/ 21 сентября 2010

Мне любопытно, как правильно обрезать строку, чтобы избежать утечки памяти. Я думаю, что это действительно вопрос, основанный на том, как именно работает 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;
}

Ответы [ 4 ]

14 голосов
/ 21 сентября 2010

Указатель, который вы передаете free, должен быть точно таким же указателем, который вы получили от malloc (или calloc или realloc), а не просто указателем на область памяти что malloc вернулся. Таким образом, ваша вторая строка - та, которая вызывает проблему. Ваш первый и третий в порядке, потому что указатель, который вы передаете free, совпадает с указателем, который вы получили от malloc (через strdup).

Однако то, что вы получаете в этом случае, на самом деле не утечка памяти, а неопределенное поведение.

5 голосов
/ 21 сентября 2010

Да, это приведет к утечке памяти, но, что еще хуже, к неопределенному поведению. Поскольку trim изменяет переменные-указатели, main передает указатель на free, который не был возвращен malloc. Это неопределенное поведение, и оно повредит кучу во многих реализациях.

Существует как минимум три правильных способа справиться с этим.

1. Обрезать, выделить и вернуть новую строку, и сделать так, чтобы вызывающая сторона отвечала за освобождение новой и старой (если необходимо):

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);
0 голосов
/ 21 сентября 2010

Вам не нужен дополнительный 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 содержимое может быть изменено.

0 голосов
/ 21 сентября 2010

Это не совсем ответ на вопрос, как работает бесплатная, но я бы сделал что-то вроде этого:

char * trim_realloc (char * str) { char * p = str; char * e; char * ne; // новый конец char * 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   "

прошел через шаги:

"с котом" "кошка кошка" "кошка Кошка " "кошка в" "кот т" "кошка"

до того, как вы начали подстригать хвост.

Теперь вернемся к тому, как работает free - free необходимо передать либо NULL, либо значение, которое вам передала одна из функций выделения кучи. Некоторые библиотеки выделения кучи реализованы так, что когда malloc выделяет данные, размер этого блока данных сохраняется в байтах непосредственно перед адресом, который возвращает malloc, и когда вы вызываете free, байты непосредственно перед этим указателем используются для определения того, что Размер этого блока памяти на самом деле. Если вы передадите что-то, что не было возвращено malloc (или calloc, или realloc, или подобным), то free может искать не в том месте и использовать все, что находит там, в качестве размера освобождаемого чанка - и ничего хорошего не получается этого.

...