Proč se dvojitý volný chybu s realloc ()?

hlasů
11

Snažil jsem se psát řetězec nahradit funkce v jazyce C, který pracuje na char *, která byla přidělena za použití malloc(). Je to trochu jiný v tom, že bude vyhledávat a nahrazovat řetězce, spíše než znaky ve výchozím řetězci.

Je to triviální dělat v případě, že vyhledávání a nahrazování řetězců mají stejnou délku (nebo nahradit řetězec kratší než hledaného řetězce), protože mám dostatek prostoru přiděleny. Když se pokusím použít realloc(), objeví se chybové hlášení, které mi říká, dělám dvojitou zdarma - což já nechápu, jak jsem, protože jsem pouze pomocí realloc().

Možná trochu kód pomůže:

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

Program funguje, dokud se snažím realloc()v případě, kdy bude nahrazen řetězec být delší než původní řetězec. (To ještě trochu práce, to prostě vyplivne chyby, stejně jako výsledek).

Pokud to pomůže, volající kód vypadá takto:

#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);
    }
}
Položena 04/08/2008 v 12:06
zdroj uživatelem
V jiných jazycích...                            


8 odpovědí

hlasů
12

Za prvé, omlouvám se, že jdu pozdě na party. Tohle je můj první Stackoverflow odpověď. :)

Jak již bylo zdůrazněno, když realloc () se nazývá, můžete případně změnit ukazatel na paměti je přidělena. Když se to stane, je argument „string“ se stává neplatným. Dokonce i když si ji přeřadit, změna dostane mimo rozsah, jakmile funkci končí.

Chcete-li odpovědět na OP, realloc () vrátí ukazatel na nově byly vyčleněny paměti. Návratová hodnota musí být uloženy někde. Obecně platí, že byste to následujícím způsobem:

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

Jako TyBoer poukazuje na to, můžete vy nezmění hodnota ukazatele byla předána jako vstup do této funkce. Můžete přiřadit, co chcete, ale změna půjde mimo rozsah na konci funkce. V následujícím bloku, „vstup“, může nebo nemusí být neplatný ukazatel, jakmile dokončí funkce:

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

Mark se snaží tento problém vyřešit tím, že vrátí nový ukazatel jako výstup z funkce. Pokud tak učiníte, že břemeno je na volající nikdy použít ukazatel se používá pro vstup. Pokud se shoduje s návratovou hodnotu, pak máte dvě ukazatele na stejném místě a jen je třeba volat zdarma () na jednom z nich. V případě, že se neshodují, vstupní ukazatel nyní odkazuje na paměti, které mohou nebo nemusí být ve vlastnictví tohoto procesu. Dereferencing to by mohlo způsobit poruchu segmentace.

Dalo by se použít dvojí ukazatel pro vstup, jako je tento:

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

Má-li volající někde duplikát vstupní ukazatele, které duplikovat stále může být nyní neplatné.

Myslím, že nejčistším řešením je zde, aby se zabránilo používání realloc (), když se snaží změnit vstup Funkce volajícího. Jen malloc () nové vyrovnávací paměti, vrátit to, a nechat volající rozhodne, zda se má uvolnit starý text. To má navíc tu výhodu, nechat volající zachovat původní řetězec!

Odpovězeno 08/08/2008 v 22:37
zdroj uživatelem

hlasů
11

Jako obecné pravidlo, měli byste nikdy dělat zdarma nebo realloc na uživatele za předpokladu, vyrovnávací paměti. Nevíš, kde je uživatel přidělen prostor (v modulu, v jiném DLL), takže nelze použít některé funkce přidělování na uživatelském vyrovnávací paměti.

Za předpokladu, že teď nemůže dělat žádné přerozdělení v rámci vaší funkci, měli byste změnit jeho chování trochu jako dělat jen jednu výměnu, takže uživatel bude moci vypočítat výslednou délku řetězce max a poskytne vám vyrovnávací paměti dostatečně dlouhá pro tento jeden Náhrada nastat.

Pak byste mohli vytvořit další funkce do činění s několika náhradníky, ale budete muset vyčlenit celý prostor pro výsledný řetězec a kopírování uživatelského vstupního řetězce. Pak je nutné zadat způsob, jak odstranit řetězec, který přidělené.

Což má za následek:

void  strrep(char *input, char *search, char *replace);
char* strrepm(char *input, char *search, char *replace);
void  strrepmfree(char *input);
Odpovězeno 04/08/2008 v 12:19
zdroj uživatelem

hlasů
6

Někdo jiný omluvil za pozdní příchod na večírek - dva a půl měsíci. No, trávím hodně času dělat software archeologii.

Mám zájem, že nikdo se vyjádřil výslovně na únik paměti v originálním designem, nebo chyby off-by-one. A pozoroval únik paměti, že mi říká přesně to, proč jste získali double bez chyb (z důvodu, abychom byli přesní, jste uvolnění stejné paměti vícekrát - a děláte tak po pošlapání nad již uvolněné paměti).

Před provedením analýzy, já souhlasím s těmi, kteří říkají, že vaše rozhraní je menší než hvězdné; Nicméně, pokud jste se zabýval únik paměti / dupání otázek a zdokumentovány požadavky ‚musí být přidělena paměť‘, mohlo by to být ‚OK‘.

Jaké jsou problémy? No, předat vyrovnávací paměti realloc () a realloc () vrátí vám nový ukazatel na oblasti byste měli použít - a budete ignorovat, že návratovou hodnotu. V důsledku toho realloc () pravděpodobně uvolněno původní paměti, a pak ji předat stejný ukazatel znovu, a to si stěžuje, že jste uvolnění stejné paměti dvakrát, protože se opět složit původní hodnotu. To nevrací nejen paměť, ale znamená to, že jste i nadále používat původní místo - a John Downey výstřel do tmy bodů na to, že jste zneužívají realloc (), avšak nezdůrazňuje, jak vážně to děláte. K dispozici je také off-by-one chyba, protože nemusíte alokovat dostatek prostoru pro NUL ‚\ 0‘, která ukončí řetězec.

Nevracení paměti dochází, protože neposkytují mechanismus sdělit volajícímu o poslední hodnoty řetězce. Vzhledem k tomu, jste si nechali pošlapání nad původní řetězec plus prostor po ní, vypadá to, že kód funguje, ale pokud váš kód volání uvolnil prostor, ale má také dostane dvojitou bez chyby, nebo může získat core dump nebo jeho ekvivalent, protože informace o řídicí paměť je zcela kódované.

Váš kód také nechrání před neurčitou růst - zvážit nahrazení ‚Noel‘ s ‚Joyeux Noel‘. Pokaždé, přidejte 7 znaků, ale byste najít jiný Noel v nahrazovaného textu, a rozšířit ji, a tak dále a tak dále. My fixup (viz níže) neřeší tento problém - jednoduché řešení je pravděpodobně zkontrolovat, zda hledaný řetězec se objeví v nahradí řetězci; alternativou je, aby přeskočit nahradit řetězec a pokračovat v hledání po něm. Druhý má některé netriviální problémy kódování k řešení.

Takže moje doporučená revize vašeho volané funkce je:

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

Tento kód nezjistí chyby přidělení paměti - a pravděpodobně dojde k chybě (ale pokud ne, nevrací paměť), pokud realloc () selže. Viz Steve Maguire ‚Psaní Solid Code‘ knihu o rozsáhlou diskusi o otázkách správy paměti.

Odpovězeno 21/10/2008 v 03:41
zdroj uživatelem

hlasů
6

Jen výstřel do tmy, protože jsem se snažil to ještě, ale když realloc vrátí ukazatel podobně jako malloc. Vzhledem k tomu, realloc může přesunout ukazatel v případě potřeby se s největší pravděpodobností působí na neplatný ukazatel, pokud nechcete provést následující kroky:

input = realloc(input, strlen(input) + delta);
Odpovězeno 04/08/2008 v 12:14
zdroj uživatelem

hlasů
4

Všimněte si, že se snaží upravit svůj kód zbavit html únikové kódy.

No, i když to bylo docela dlouho, co jsem používal C / C ++, realloc že roste opakovaně pouze ukazatel hodnotu velikosti paměti, pokud je zde prostor v paměti poté, co původní bloku.

Například, zvažovat toto:

(Xxxxxxxxxx ..........)

Pokud ukazatel ukazuje na první xa. znamená, že volné místo v paměti, a ty rostou velikost paměti poukázal na vaší proměnné 5 bajtů, bude to úspěch. To je samozřejmě zjednodušený příklad jsou bloky se zaokrouhlují na určité velikosti pro vyrovnání, ale tak jako tak.

Nicméně, pokud se následně snažit pěstovat ji dalších 10 bytů, a tam je k dispozici pouze 5, bude muset přesunout blok do paměti a aktualizovat svůj ukazatel.

Avšak ve svém příkladu jsou předávání Funkci ukazatel na znak, ne ukazatel na proměnné, a tak zatímco funkce strrep vnitřně měli být schopni nastavit proměnnou v užívání, je to lokální proměnné funkce strrep a Vaše volání kódu budou ponechány původní hodnoty proměnné ukazatel.

Tento ukazatel hodnota však byla uvolněna.

Ve vašem případě, vstup je viník.

Nicméně bych udělat další návrh. Ve vašem případě to vypadá vstupní proměnná je opravdu vstup, a pokud ano, neměl by být změněn, vůbec.

Chtěl bych proto se snaží najít jiný způsob, jak dělat to, co chcete dělat, aniž by se změnila vstup , protože vedlejší účinky, jako to může být těžké vystopovat.

Odpovězeno 04/08/2008 v 12:17
zdroj uživatelem

hlasů
3

realloc je zvláštní, komplikovaná a měl by být používán pouze při jednání s velkým množstvím paměti hodně krát za sekundu. tj - kde to vlastně dělá váš kód rychleji.

Viděl jsem kód, kde

realloc(bytes, smallerSize);

byl použit a snažil se změnit velikost vyrovnávací paměti, což je menší. Pracoval asi miliónkrát, pak z nějakého důvodu realloc rozhodl, že i když jste byli zkrácením vyrovnávací paměti, to by vám pěknou novou kopii. Tak narazíte v náhodném místě 1/2 sekundy po špatné věci se stalo.

Vždy používat návratovou hodnotu realloc.

Odpovězeno 16/05/2011 v 23:57
zdroj uživatelem

hlasů
3

To zdá se k práci;

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

Povzdech, je tam tak jako tak, abyste mohl psát kód, aniž by sání?

Odpovězeno 04/08/2008 v 12:39
zdroj uživatelem

hlasů
0

Mé rychlé tipy.

Namísto:
void strrep(char *input, char *search, char *replace)
try:
void strrep(char *&input, char *search, char *replace)

a pak v těle:
input = realloc(input, strlen(input) + delta);

Obecně si o předávání argumenty funkce jako hodnoty / reference a realloc () popis :).

Odpovězeno 04/08/2008 v 12:20
zdroj uživatelem

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more