C вопрос выделения памяти - PullRequest
0 голосов
/ 16 марта 2009

Итак, у меня есть пара функций, которые работают с типом string, который я создал. Один из них создает динамически распределенное укус. Другой берет указанную строку и расширяет ее. И последний освобождает строку. Примечание: имена функций изменены, но все они определены мной.

string new = make("Hello, ");
adds(new, "everyone");
free(new);

Код выше работает - он компилируется и работает нормально. Код ниже не работает - он компилируется, запускается, а затем

string new = make("Hello, ");
adds(new, "everyone!");
free(new);

Разница между кодом заключается в том, что функция adds() добавляет еще 1 символ (!). Характер, который он добавляет, не имеет значения - только длина. Просто для полноты, следующий код не работает:

string new = make("Hello, ");
adds(new, "everyone");
adds(new, "!");
free(new);

Как ни странно, работает следующий код, который использует другую функцию, addc() (который добавляет 1 символ вместо строки):

string new = make("Hello, ");
adds(new, "everyone");
addc(new, '!');
free(new);

Работает следующее, что также делает то же самое:

string new = make("Hello, everyone!");
free(new);

Ошибка, которую выдают все те, кто не работает:

test(526) malloc: *** error for object 0x100130: double free
*** set a breakpoint in malloc_error_break to debug

(test - чрезвычайно описательное название программы, в которой я это использую.)

Что касается внутренних функций, мой make() - это вызов strlen() и два вызова на malloc() и вызов memcpy(), мой adds() - это вызов strlen(), вызов на realloc() и вызов на memcpy(), а мой free() - это два вызова на стандартную библиотеку free().

Так есть ли идеи, почему я получаю это, или мне нужно сломаться и использовать отладчик? Я получаю его только с adds() es определенной длины, а не с addc() s.

Разбивка и размещение кода для функций:

typedef struct _str {
  int _len;
  char *_str;
} *string;

string make(char *c)
{
    string s = malloc(sizeof(string));
    if(s == NULL) return NULL;
    s->_len = strlen(c);
    s->_str = malloc(s->_len + 1);
    if(s->_str == NULL) 
      {     
        free(s);
        return NULL;
      }
    memcpy(s->_str, c, s->_len);
    return s;
}

int adds(string s, char *c)
{
    int l = strlen(c);
    char *tmp;
    if(l <= 0) return -1;
    tmp = realloc(s->_str, s->_len + l + 1);
    if(!tmp) return 0;
    memcpy(s->_str + s->_len, c, l);
    s->_len += l;
    s->_str[s->_len] = 0;
    return s->_len;
}

void myfree(string s)
{
    if(s->_str) free(s->_str);
    free(s);
    s = NULL;
    return;
}

Ответы [ 6 ]

4 голосов
/ 16 марта 2009

Ряд потенциальных проблем, которые я бы исправил:

1 / Ваш make() опасен, так как он не копирует строку через нулевой терминатор.

2 / Также не имеет смысла устанавливать s на NULL в myfree(), поскольку это переданный параметр и не повлияет на фактический переданный параметр.

3 / Я не уверен, почему вы возвращаете -1 из adds(), если добавленная длина строки равна 0 или меньше. Во-первых, это не может быть отрицательным. Во-вторых, кажется вполне вероятным, что вы можете добавить пустую строку, что должно привести к тому, что строка не будет изменена и не будет возвращена текущая длина строки. Я бы только возвратил длину -1, если это не удалось (т. Е. realloc() не сработало), и убедился бы, что старая строка сохраняется, если это произойдет.

4 / Вы не сохраняете переменную tmp в s->_str, даже если она может измениться - она ​​редко перераспределяет память на месте, если вы увеличиваете размер, хотя это возможно, если увеличение небольшое достаточно, чтобы поместиться в любое дополнительное пространство, выделенное malloc(). Уменьшение размера почти наверняка приведет к перераспределению на месте, если ваша реализация malloc() не использует разные пулы буферов для блоков памяти разного размера. Но это только в стороне, так как вы никогда не уменьшаете использование памяти с этим кодом.

5 / Я думаю, что ваша специфическая проблема в том, что вы выделяете место только для строки, которая является указателем на структуру, а не на саму структуру. Это означает, что когда вы помещаете строку, вы портите область памяти.

Это код, который я бы написал (включая более описательные имена переменных, но это только мои предпочтения).

Я изменился:

  • возвращаемые значения от adds() для лучшего отражения длины и условий ошибки. Теперь он возвращает -1 только в том случае, если не может раскрыться (а исходная строка не затронута) - любое другое возвращаемое значение - это длина новой строки.
  • возврат из myfree(), если вы действительно хотите установить строку в NULL с чем-то вроде "s = myfree (s)".
  • проверяет myfree() для строки NULL, поскольку теперь вы никогда не можете иметь выделенное string без выделенного string->strChars.

Вот, используйте (или не используйте :-), как считаете нужным):

/*================================*/
/* Structure for storing strings. */

typedef struct _string {
    int  strLen;     /* Length of string */
    char *strChars;  /* Pointer to null-terminated chars */
} *string;

/*=========================================*/
/* Make a string, based on a char pointer. */

string make (char *srcChars) {
    /* Get the structure memory. */

    string newStr = malloc (sizeof (struct _string));
    if (newStr == NULL)
        return NULL;

    /* Get the character array memory based on length, free the
       structure if this cannot be done. */

    newStr->strLen = strlen (srcChars);
    newStr->strChars = malloc (newStr->strLen + 1);
    if(newStr->strChars == NULL) {     
        free(newStr);
        return NULL;
    }

    /* Copy in string and return the address. */

    strcpy (newStr->strChars, srcChars);
    return newStr;
}

/*======================================================*/
/* Add a char pointer to the end of an existing string. */

int adds (string curStr, char *addChars) {
    char *tmpChars;

    /* If adding nothing, leave it alone and return current length. */

    int addLen = strlen (addChars);
    if (addLen == 0)
        return curStr->strLen;

    /* Allocate space for new string, return error if cannot be done,
       but leave current string alone in that case. */

    tmpChars = malloc (curStr->strLen + addLen + 1);
    if (tmpChars == NULL)
        return -1;

    /* Copy in old string, append new string. */

    strcpy (tmpChars, curStr->strChars);
    strcat (tmpChars, addChars);

    /* Free old string, use new string, adjust length. */

    free (curStr->strChars);
    curStr->strLen = strlen (tmpChars);
    curStr->strChars = tmpChars;

    /* Return new length. */

    return curStr->strLen;
}

/*================*/
/* Free a string. */

string myfree (string curStr) {
    /* Don't mess up if string is already NULL. */

    if (curStr != NULL) {
        /* Free chars and the string structure. */

        free (curStr->strChars);
        free (curStr);
    }

    /* Return NULL so user can store that in string, such as
       <s = myfree (s);> */

    return NULL;
}

Единственное другое возможное улучшение, которое я мог видеть, - это сохранение буфера пространства и конца strChars для обеспечения уровня расширения без вызова malloc().

Для этого потребуются как длина буфера, так и длина строки, а также изменение кода, чтобы выделить больше места только в том случае, если общая длина строки и длина новых символов больше, чем длина буфера.

Все это будет заключено в функцию, чтобы API не изменился вообще. И, если вам когда-нибудь понадобится предоставить функции для уменьшения размера строки, им также не придется перераспределять память, они просто уменьшат использование буфера. В этом случае вам, вероятно, потребуется функция compress(), чтобы уменьшить количество строк с большим буфером и небольшой строкой.

4 голосов
/ 16 марта 2009

Первый malloc в make должен быть:

malloc (sizeof (struct _str));

В противном случае вы выделяете достаточно места только для указателя до struct _str.

2 голосов
/ 16 марта 2009
 tmp = realloc(s->_str, s->_len + l + 1);

realloc может вернуть новый указатель на запрошенный блок. Вам необходимо добавить следующую строку кода:

 s->_str = tmp;

Причина, по которой он не падает в одном случае, а происходит после добавления еще одного символа, возможно, только из-за того, как выделяется память. Вероятно, существует минимальная дельта распределения (в данном случае 16). Поэтому, когда вы выделяете первые 8 символов для приветствия, он фактически выделяет 16. Когда вы добавляете всех, он не превышает 16, так что вы получаете исходный блок обратно. Но для 17 символов realloc возвращает новый буфер памяти.

Попробуйте изменить добавление следующим образом

 tmp = realloc(s->_str, s->_len + l + 1);
 if (!tmp) return 0;
 if (tmp != s->_str) {
     printf("Block moved!\n"); // for debugging
     s->_str = tmp;
 }
1 голос
/ 16 марта 2009

В функции adds предполагается, что realloc не изменяет адрес блока памяти, который необходимо перераспределить:

tmp = realloc(s->_str, s->_len + l + 1);
if(!tmp) return 0;
memcpy(s->_str + s->_len, c, l);

Хотя это может быть справедливо для небольших перераспределений (поскольку размеры блоков памяти, которые вы получаете, обычно округляются для оптимизации распределения), в целом это не так. Когда realloc возвращает вам новый указатель, ваша программа все еще использует старый, вызывая проблему:

memcpy(s->_str + s->_len, c, l);
0 голосов
/ 16 марта 2009

Почему «my free () - это два вызова стандартной библиотеки free ()». Почему ты звонишь бесплатно дважды? Вам нужно позвонить только один раз.

Пожалуйста, опубликуйте свои добавления (); и функции free ().

0 голосов
/ 16 марта 2009

Вероятно, следует опубликовать код, но двойное освобождение означает, что вы звоните бесплатно по одному и тому же указателю дважды.

  1. Добавляете ли вы 1 в strlen для байта \ 0 в конце?
  2. Как только вы освободите указатель, вы устанавливаете переменную-член в значение NULL, чтобы вы не могли снова освободиться (или для известного плохого указателя, такого как 0xFFFFFFFF)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...