Сведения о вопросе

CPdeveloper

16:03, 1st July, 2020

Теги

c   malloc    

Почему я получаю двойную свободную ошибку с realloc()?

Просмотров: 359   Ответов: 8

Я попытался написать функцию замены строки в C , которая работает на char *, который был выделен с помощью malloc() . Это немного отличается тем, что он будет находить и заменять строки, а не символы в начальной строке.

Это тривиально, если строки поиска и замены имеют одинаковую длину (или строка замены короче строки поиска), так как у меня достаточно места. Если я попытаюсь использовать 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");
    }
}



  Сведения об ответе

baggs

18:03, 1st July, 2020

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

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

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

В результате чего:

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


  Сведения об ответе

prince

18:03, 1st July, 2020

Во-первых, извини, что опоздал на вечеринку. Это мой первый ответ 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, вы, ребята, не можете изменить значение указателя, передаваемого в качестве входных данных для этой функции. Вы можете назначить все, что хотите, но это изменение выйдет за пределы области действия в конце функции. В следующем блоке "input" может быть или не быть недопустимым указателем после завершения функции:

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));
}

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

Вы можете использовать двойной указатель для ввода, как это:

void foobar(char **input, int newlength)
{
   *input = realloc(*input, newlength * sizeof(char));
}

Если у вызывающего абонента где-то есть дубликат входного указателя, этот дубликат все еще может быть недействительным.

Я думаю, что самое чистое решение здесь заключается в том, чтобы избежать использования realloc() при попытке изменить входные данные вызывающей функции. Просто malloc() новый буфер, верните его, и пусть вызывающий решает, освобождать ли старый текст или нет. Это имеет дополнительное преимущество, позволяя вызывающему сохранить исходную строку!


  Сведения об ответе

prince

18:03, 1st July, 2020

Просто выстрел в темноте, потому что я еще не пробовал его, но когда вы перераспределяете его, он возвращает указатель так же, как malloc. Поскольку realloc может перемещать указатель в случае необходимости вы скорее всего работаете с недопустимым указателем если вы не делаете следующее:

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


  Сведения об ответе

lesha

18:03, 1st July, 2020

Кто - то еще извинился за опоздание на вечеринку-два с половиной месяца назад. Ну что ж, я провожу довольно много времени, занимаясь программной археологией.

Мне интересно, что никто не прокомментировал явно утечку памяти в оригинальном дизайне или ошибку off-by-one. И именно наблюдение за утечкой памяти говорит мне точно, почему вы получаете двойную свободную ошибку (потому что, если быть точным, вы освобождаете одну и ту же память несколько раз - и делаете это после того, как топчете уже освобожденную память).

Прежде чем проводить анализ, я соглашусь с теми, кто говорит, что ваш интерфейс менее звездный; однако, если вы имели дело с проблемами утечки памяти/вытаптывания и задокументировали требование "должна быть выделена память", это может быть 'OK'.

В чем же заключаются проблемы? Ну, вы передаете буфер в realloc(), и realloc() возвращает вам новый указатель на область, которую вы должны использовать - и вы игнорируете это возвращаемое значение. Следовательно, realloc(), вероятно, освободил исходную память, а затем вы передаете ему тот же указатель снова, и он жалуется, что вы освобождаете ту же самую память дважды, потому что вы снова передаете ему исходное значение. Это не только утечка памяти, но и означает, что вы продолжаете использовать исходное пространство-и снимок Джона Дауни в темноте указывает на то, что вы злоупотребляете realloc(), но не подчеркивает, насколько серьезно вы это делаете. Существует также ошибка off-by-one, потому что вы не выделяете достаточно места для NUL '\0', который завершает строку.

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

Ваш код также не защищает от неопределенного роста-подумайте о замене 'Noel' на 'Joyeux Noel'. Каждый раз вы добавляете 7 символов, но в замененном тексте находите еще один Ноэль и расширяете его, и так далее, и так далее. Мое исправление (ниже) не решает эту проблему - простое решение, вероятно, состоит в том, чтобы проверить, появляется ли строка поиска в строке замены; альтернативой является пропустить строку замены и продолжить поиск после нее. Во втором случае необходимо решить некоторые нетривиальные вопросы кодирования.

Итак, я предлагаю пересмотреть вашу вызываемую функцию:

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() не удается. В книге Стива Магуайра "написание твердого кода" подробно обсуждаются вопросы управления памятью.


  Сведения об ответе

KOMP

18:03, 1st July, 2020

Обратите внимание, попробуйте отредактировать свой код, чтобы избавиться от html escape-кодов.

Ну, хотя прошло уже некоторое время с тех пор, как я использовал C/C++, realloc, который растет, только повторно использует значение указателя памяти, если есть место в памяти после вашего исходного блока.

Например, рассмотрим это:

(xxxxxxxxxx..........)

Если ваш указатель указывает на первый x, и . означает свободное место в памяти, и вы увеличиваете размер памяти, указанный вашей переменной, на 5 байт, это будет успешно. Это, конечно, упрощенный пример, поскольку блоки округляются до определенного размера для выравнивания, но все равно.

Однако, если вы впоследствии попытаетесь увеличить его еще на 10 байт, а доступно будет только 5, ему потребуется переместить блок в память и обновить указатель.

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

Однако это значение указателя было освобождено.

В вашем случае виновником является ввод данных.

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

Таким образом, я бы попытался найти другой способ сделать то , что вы хотите сделать, без изменения входных данных, поскольку побочные эффекты, подобные этому, могут быть трудно отследить.


  Сведения об ответе

9090

18:03, 1st July, 2020

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

Я видел код где

realloc(bytes, smallerSize);

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

Всегда используйте возвращаемое значение realloc.


  Сведения об ответе

LAST

18:03, 1st July, 2020

Это, кажется, работает;

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;
}

Вздох, есть ли вообще почтовый индекс без этого сосания?


  Сведения об ответе

lats

18:03, 1st July, 2020

Мои быстрые намеки.

Вместо:
void strrep(char *input, char *search, char *replace)
пробовать:
void strrep(char *&input, char *search, char *replace)

и чем в организме:
input = realloc(input, strlen(input) + delta);

Обычно читаем о передаче аргументов функции в виде значений / reference и realloc() description :).


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться