Hvorfor får jeg en dobbel gratis feil med realloc ()?

stemmer
11

Jeg har prøvd å skrive en streng erstatte funksjon i C, som fungerer på en char *, som har blitt tildelt hjelp malloc(). Det er litt annerledes ved at den vil finne og erstatte strenger, snarere enn tegn på start streng.

Det er trivielt å gjøre hvis søke og erstatte strenger er like lange (eller erstatte strengen er kortere enn søkestrengen), siden jeg har nok plass tildelt. Hvis jeg prøver å bruke realloc(), får jeg en feilmelding som forteller meg jeg gjør en dobbel gratis - som jeg ikke ser hvordan jeg er, siden jeg bare bruke realloc().

Kanskje litt kode vil hjelpe:

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

Programmet fungerer, før jeg prøver å realloc()i et tilfelle hvor det erstattede strengen blir lengre enn den første strengen. (Det fortsatt slags fungerer, det bare spytter ut feil så vel som resultat).

Hvis det hjelper, ser ringer koden slik ut:

#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);
    }
}
Publisert på 04/08/2008 klokken 11:06
kilden bruker
På andre språk...                            


8 svar

stemmer
12

First off, beklager jeg er sent til festen. Dette er min første Stackoverflow svar. :)

Som det har blitt påpekt, når realloc () kalles, du kan potensielt endre pekeren til minnet blir omdisponert. Når dette skjer, blir argumentet "streng" ugyldig. Selv om du tilordne den, går endringen ut av rammen når funksjonen avsluttes.

For å svare på OP, realloc () returnerer en peker til den nylig tildelt nytt minne. Returverdien må lagres et sted. Vanligvis ville du gjøre dette:

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

Som TyBoer påpeker, kan dere ikke endre verdien av pekeren blir vedtatt inn som innspill til denne funksjonen. Du kan tilordne hva du vil, men endringen vil gå ut av rammen ved slutten av funksjonen. I den etterfølgende blokk, "input" kan eller kan ikke være en ugyldig pekeren når funksjonen er fullført:

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 forsøker å omgå dette ved å returnere den nye pekeren som output av funksjonen. Hvis du gjør det, er onus på den som ringer å aldri bruke pekeren han brukte for innspill. Hvis det stemmer returverdien, da har du to pekere til samme sted og trenger bare å ringe gratis () på en av dem. Hvis de ikke stemmer overens, inngangs pekeren peker nå til minne som kan eller ikke kan være eid av prosessen. Dereferencing det kunne føre til en segmentering feil.

Du kan bruke en dobbel pekeren for innspill, som dette:

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

Hvis den som ringer har en kopi av innspill peker et sted, som duplisere fortsatt kan være ugyldig nå.

Jeg tror den reneste løsningen her er å unngå å bruke realloc () når du prøver å endre funksjonen som ringer inn. Bare malloc () en ny buffer, returnere det, og la den som ringer avgjøre om ikke å frigjøre den gamle teksten. Dette har den ekstra fordelen av å la den som ringer beholde den opprinnelige strengen!

Svarte 08/08/2008 kl. 21:37
kilden bruker

stemmer
11

Som en generell regel bør du aldri gjøre en gratis eller realloc på brukerens gitt buffer. Du vet ikke hvor brukeren tildelt plass (i modul, i en annen DLL) slik at du ikke kan bruke noen av tildelings funksjoner på en brukers buffer.

Forutsatt at du nå ikke kan gjøre noe omdisponering innenfor funksjon din, bør du endre sin oppførsel litt, som gjør bare en erstatning, slik at brukeren vil kunne beregne den resulterende strengen maks lengde og gi deg en buffer lenge nok for en erstatning skal skje.

Deretter kan du opprette en annen funksjon for å gjøre flere utskiftninger, men du er nødt til å fordele hele rommet for den resulterende strengen og kopiere brukerundersøkelser streng. Da må du gi en måte å slette strengen du tildelt.

Resulterer i:

void  strrep(char *input, char *search, char *replace);
char* strrepm(char *input, char *search, char *replace);
void  strrepmfree(char *input);
Svarte 04/08/2008 kl. 11:19
kilden bruker

stemmer
6

Noen andre beklaget for å være sent til festen - to og en halv måned siden. Jaja, jeg bruke ganske mye tid på å gjøre programvaren arkeologi.

Jeg er interessert i at ingen har kommen eksplisitt på minnelekkasje i den opprinnelige design, eller off-for-én feil. Og det var å observere minnelekkasje som forteller meg nøyaktig hvorfor du får dobbel-frie feil (fordi, for å være presis, er du frigjør de samme minne flere ganger - og du gjør det etter tråkke over allerede frigjort minne).

Før gjennomføre analysen, vil jeg være enig med dem som sier grensesnittet er mindre enn fantastisk; men hvis du jobbet med minnelekkasje / tråkker problemer og dokumentert 'må bli allokert minne' krav, kan det være OK.

Hva er problemet? Vel, du passerer en buffer for å realloc (), og realloc () returnerer en ny peker til området du skal bruke - og du ignorerer at returverdi. Derfor realloc () har trolig frigjort den opprinnelige minnet, og deretter gi det samme pekeren igjen, og det klager på at du frigjør samme minne to ganger fordi du passerer den opprinnelige verdien til det igjen. Dette ikke bare lekker minne, men betyr at du fortsetter å bruke den opprinnelige plass - og John Downey sitt skudd i mørket påpeker at du misbruker realloc (), men ikke understreke hvor alvorlig du gjør det. Det er også en off-by-en feil fordi du ikke bevilge nok plass for NUL '\ 0' som avslutter strengen.

Den minnelekkasje oppstår fordi du ikke gir en mekanisme for å fortelle den som ringer om den siste verdien av strengen. Fordi du holdt tråkke over den opprinnelige strengen pluss mellomrom etter det, det ser ut som koden arbeidet, men hvis du ringer koden frigjort plass, det også ville få en dobbel-free feil, eller det kan bli en kjerne dump eller tilsvarende fordi lagerstyringsinformasjon er helt forvrengt.

Koden beskytter heller ikke mot ubestemt vekst - vurdere å erstatte 'Noel' med 'En dag uten krig'. Hver gang, vil du legge til 7 tegn, men du vil finne en annen Noel i erstattet tekst, og utvide den, og så videre og så videre. Min retting (nedenfor) løser ikke dette problemet - den enkle løsningen er trolig å sjekke om søkestrengen vises i erstatte strengen; et alternativ er å hoppe over erstatte strengen og fortsette søket etter den. Den andre har noen ikke-trivielle koding problemer å løse.

Så, mitt foreslåtte revisjon av din heter funksjonen er:

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

Denne koden oppdager ikke minnetildeling feil - og trolig krasjer (men hvis ikke, lekker minne) hvis realloc () svikter. Se Steve Maguire 'Skrive Solid Kode' bok for en omfattende diskusjon av minne administrative spørsmål.

Svarte 21/10/2008 kl. 02:41
kilden bruker

stemmer
6

Bare et skudd i mørket, fordi jeg ikke har prøvd det ennå, men når du realloc den returnerer pekeren mye som malloc. Fordi realloc kan flytte pekeren ved behov du mest sannsynlig operere på en ugyldig peker hvis du ikke gjør følgende:

input = realloc(input, strlen(input) + delta);
Svarte 04/08/2008 kl. 11:14
kilden bruker

stemmer
4

Merk, prøv å redigere koden for å kvitte seg med html rømnings koder.

Vel, om det har vært en stund siden jeg brukte C / C ++, realloc som vokser bare gjenbruker pekeren verdi minnet hvis det er plass i minnet etter at den opprinnelige blokken.

For eksempel, tenk på dette:

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

Hvis pekeren peker til de første x, og. betyr gratis minneplassering, og du blir minnet størrelse peker til variabelen med 5 byte, det vil lykkes. Dette er selvfølgelig en forenklet eksempel som blokker er rundet opp til en viss størrelse for justering, men likevel.

Men hvis du senere prøver å vokse det av en annen 10 bytes, og det er bare fem tilgjengelig, vil det være behov for å flytte blokken i minnet og oppdatere pekeren.

Men i ditt eksempel er du passerer funksjonen en peker til karakteren, ikke en peker til variabel, og dermed mens strrep funksjonen internt kan være i stand til å justere den variable i bruk, er det en lokal variabel til strrep funksjon og din ringer koden vil sitte igjen med den opprinnelige pekeren variabel verdi.

Dette pekerverdien, men har blitt frigjort.

I ditt tilfelle, er inngangen den skyldige.

Men jeg ville gjøre et annet forslag. I ditt tilfelle ser det ut til innspill variabelen er faktisk inngang, og hvis det er, bør det ikke bli endret, i det hele tatt.

Jeg vil derfor prøve å finne en annen måte å gjøre hva du vil gjøre, uten å endre innspill , som bivirkninger som dette kan være vanskelig å spore opp.

Svarte 04/08/2008 kl. 11:17
kilden bruker

stemmer
3

realloc er merkelig, komplisert og bør bare brukes når du arbeider med mange minne mange ganger per sekund. det vil si - hvor det faktisk gjør koden din raskere.

Jeg har sett koden der

realloc(bytes, smallerSize);

ble brukt og jobbet for å endre størrelsen på buffer, noe som gjør det mindre. Jobbet ca en million ganger, så en eller annen grunn realloc besluttet at selv om du forkorte buffer, vil det gi deg en fin ny kopi. Så du krasjer i et tilfeldig sted 1/2 sekund etter den dårlige ting skjedde.

Bruk alltid returverdien av realloc.

Svarte 16/05/2011 kl. 22:57
kilden bruker

stemmer
3

Dette ser ut til å fungere,

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

Sukk, er det allikevel å legge koden uten det suger?

Svarte 04/08/2008 kl. 11:39
kilden bruker

stemmer
0

Min raske tips.

I stedet for:
void strrep(char *input, char *search, char *replace)
prøve:
void strrep(char *&input, char *search, char *replace)

og enn i kroppen:
input = realloc(input, strlen(input) + delta);

Vanligvis lese om sending av funksjons argumenter som verdier / referanse og realloc () :) beskrivelse.

Svarte 04/08/2008 kl. 11:20
kilden bruker

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