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

Я попытался написать функцию замены строки в 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");
    }
}

Ответ 1

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

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

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

Результат:

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

Ответ 2

Прежде всего, извините, я опаздываю на вечеринку. Это мой первый ответ stackoverflow.:)

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

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

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() новый буфер, верните его и позвольте вызывающему абоненту решить, следует ли освобождать старый текст. Это дает дополнительное преимущество, позволяя вызывающему абоненту сохранить исходную строку!

Ответ 3

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

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

Ответ 4

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

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

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

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

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

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

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

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

Ответ 5

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

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

Например, рассмотрим следующее:

(XXXXXXXXXX..........)

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

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

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

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

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

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

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

Ответ 6

Это работает,

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

Вздох, есть ли вообще сообщение с кодом без его сосания?

Ответ 7

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

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

realloc(bytes, smallerSize);

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

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

Ответ 8

Мои быстрые подсказки.

Вместо:
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:).