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

StackOverflow https://stackoverflow.com/questions/1145

  •  08-06-2019
  •  | 
  •  

Вопрос

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

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

Решение

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

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

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

Приводящий к:

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

Другие советы

Во-первых, извини, что я опоздал на вечеринку.Это мой первый ответ stackoverflow.:)

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

Чтобы ответить на запрос, 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);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

(хххххххххх..........)

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

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

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

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

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

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

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

Кажется, это работает;

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

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

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

Я видел код, в котором

realloc(bytes, smallerSize);

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

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

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

Вместо того, чтобы:
void strrep(char *input, char *search, char *replace)
попробуй:
void strrep(char *&input, char *search, char *replace)

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top