Почему при использовании функции 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");
    }
}
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
16
0
7 819
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

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

И если realloc завершается неудачно, он возвращает NULL и оставляет существующий буфер в покое. Вы только что потеряли указатель ... :-(

Roger Lipscombe 09.07.2009 19:35

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

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

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

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

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

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

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

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

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

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

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

Ответ принят как подходящий

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

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

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

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

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

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

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

а чем в кузове:
input = realloc(input, strlen(input) + delta);

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

Обозначение void strrep(char *&input, char *search, char *replace) недействительно в C, но допустимо в C++. Вопрос не в том, и AFAICT никогда не был помечен C++. В лучшем случае код должен быть void strrep(char **input, char *search, char *replace), хотя легко утверждать, что char *strrep(const char *input, const char *search, const char *replace) - работоспособный интерфейс (входные строки не меняются; измененная строка выделяется и возвращается).

Jonathan Leffler 27.05.2016 22:04

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

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

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

Добавление комментария, поскольку комментарий был написан как ответ, до того, как комментирование было доступно: похоже, это меняет только первое вхождение. Что, вероятно, разумно, так как я на самом деле не утверждал, что он должен изменить их все!

Matthew Schinckel 14.08.2015 04:42

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

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

Matthew Schinckel 22.11.2008 16:29

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

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

realloc(bytes, smallerSize);

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

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

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