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

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

Ответов (8)

Решение

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

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

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

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

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

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

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

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

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

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

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

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

Ваш код также не защищает от неограниченного роста - подумайте о замене «Ноэль» на «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 () не работает. См. Книгу Стива Магуайра «Writing Solid Code», где подробно обсуждаются вопросы управления памятью.

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

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

Вздох, а есть ли способ разместить код без отстой?

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

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

а чем в теле:
input = realloc(input, strlen(input) + delta);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

realloc(bytes, smallerSize);

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

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