Aquí podría ser tu PUBLICIDAD


¿Por qué obtengo un doble error libre con realloc ()?

votos
11

Intenté escribir una función de reemplazo de cadenas en C, que funciona en a char *, que se ha asignado usando malloc(). Es un poco diferente ya que encontrará y reemplazará cadenas, en lugar de caracteres en la cadena de inicio.

Es trivial si las cadenas de búsqueda y reemplazo tienen la misma longitud (o la cadena de reemplazo es más corta que la cadena de búsqueda), ya que tengo espacio suficiente asignado. Si trato de usar realloc(), recibo un error que me dice que estoy haciendo una doble libre, lo que no veo como soy, ya que solo estoy usando realloc().

Tal vez un pequeño código ayude:

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

El programa funciona, hasta que intento hacerlo realloc()en una instancia donde la cadena reemplazada será más larga que la cadena inicial. (Todavía funciona, solo arroja errores y el resultado).

Si ayuda, el código de llamada se ve así:

#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);
    }
}
Publicado el 04/08/2008 a las 12:06
fuente por usuario Matthew Schinckel
En otros idiomas...        العربية       

8 respuestas

votos
12

Primero, lamento llegar tarde a la fiesta. Esta es mi primera respuesta de stackoverflow. :)

Como se ha señalado, cuando se llama a realloc (), puede cambiar potencialmente el puntero a la memoria que se reasigna. Cuando esto sucede, el argumento "cadena" deja de ser válido. Incluso si lo reasigna, el cambio queda fuera del alcance una vez que la función finaliza.

Para responder al OP, realloc () devuelve un puntero a la memoria recién reasignada. El valor de retorno debe almacenarse en algún lugar. En general, harías esto:

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

Como señala TyBoer, ustedes no pueden cambiar el valor del puntero que se pasa como entrada a esta función. Puede asignar lo que desee, pero el cambio saldrá del alcance al final de la función. En el siguiente bloque, "entrada" puede ser o no un puntero no válido una vez que la función se completa:

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 intenta evitar esto devolviendo el nuevo puntero como el resultado de la función. Si lo hace, la persona que llama tiene la responsabilidad de no volver a utilizar el puntero que utilizó para ingresar. Si coincide con el valor de retorno, tiene dos punteros en el mismo lugar y solo necesita llamar a free () en uno de ellos. Si no coinciden, el puntero de entrada ahora apunta a la memoria que puede o no ser propiedad del proceso. La desreferenciación podría causar una falla de segmentación.

Podría usar un doble puntero para la entrada, así:

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

Si la persona que llama tiene un duplicado del puntero de entrada en alguna parte, ese duplicado aún puede ser no válido ahora.

Creo que la solución más limpia es evitar el uso de realloc () cuando se intenta modificar la entrada de la persona que llama a la función. Solo malloc () un nuevo búfer, devuélvalo y deje que quien llama decida si desea o no liberar el texto anterior. ¡Esto tiene el beneficio adicional de permitir que la persona que llama conserve la secuencia original!

Respondida el 08/08/2008 a las 10:37
fuente por usuario Tryke


Aquí podría ser tu PUBLICIDAD


votos
11

Como regla general, nunca debe hacer un búfer libre o real en un búfer proporcionado por el usuario. No sabe dónde asignó el usuario el espacio (en su módulo, en otra DLL) por lo que no puede usar ninguna de las funciones de asignación en un búfer de usuario.

Dado que ahora no puede hacer ninguna reasignación dentro de su función, debe cambiar un poco su comportamiento, como hacer solo un reemplazo, para que el usuario pueda calcular la longitud máxima de la cadena resultante y proporcionarle un buffer lo suficientemente largo para esta reemplazo para ocurrir.

Entonces podría crear otra función para hacer los reemplazos múltiples, pero deberá asignar todo el espacio para la cadena resultante y copiar la cadena de entrada del usuario. Luego debe proporcionar una forma de eliminar la cadena que asignó.

Resultando en:

void  strrep(char *input, char *search, char *replace);
char* strrepm(char *input, char *search, char *replace);
void  strrepmfree(char *input);
Respondida el 04/08/2008 a las 12:19
fuente por usuario Vincent Robert

votos
6

Alguien más se disculpó por llegar tarde a la fiesta, hace dos meses y medio. Oh, bueno, paso bastante tiempo haciendo arqueología de software.

Me interesa que nadie haya comentado explícitamente sobre la fuga de memoria en el diseño original o el error "uno por uno". Y fue observar la fuga de memoria que me dice exactamente por qué está obteniendo el error de doble liberación (porque, para ser precisos, está liberando la misma memoria varias veces, y lo está haciendo después de pisotear la memoria ya liberada).

Antes de realizar el análisis, estaré de acuerdo con aquellos que dicen que su interfaz es menos que estelar; sin embargo, si resolvió los problemas de fuga de memoria / pisoteo y documentó el requisito de "memoria asignada", podría ser "OK".

¿Cuáles son los problemas? Bueno, pasas un buffer a realloc (), y realloc () te devuelve un nuevo puntero al área que deberías usar e ignoras ese valor de retorno. En consecuencia, es probable que realloc () haya liberado la memoria original, y luego le pase el mismo puntero nuevamente, y se queja de que está liberando la misma memoria dos veces porque le vuelve a pasar el valor original. Esto no solo filtra la memoria, sino que significa que continúa utilizando el espacio original, y la foto de John Downey en la oscuridad señala que está haciendo un uso incorrecto de realloc (), pero no enfatiza qué tan severamente lo está haciendo. También hay un error "off-by-one" porque no asigna suficiente espacio para el NUL '\ 0' que termina la cadena.

La pérdida de memoria se produce porque no proporciona un mecanismo para decirle a la persona que llama sobre el último valor de la cadena. Debido a que seguiste pisoteando la cadena original más el espacio posterior, parece que el código funcionó, pero si tu código de llamada liberó el espacio, también obtendría un doble error libre, o podría obtener un volcado principal o equivalente porque la información de control de memoria está completamente codificada.

Su código tampoco protege contra el crecimiento indefinido; considere reemplazar 'Noel' por 'Joyeux Noel'. Cada vez, agregaría 7 caracteres, pero encontraría otro Noel en el texto reemplazado y lo expandiría, y así sucesivamente. Mi corrección (a continuación) no resuelve este problema; la solución simple es, probablemente, verificar si la cadena de búsqueda aparece en la cadena de reemplazo; una alternativa es omitir la cadena de reemplazo y continuar la búsqueda después de ella. El segundo tiene algunos problemas de codificación no triviales para abordar.

Entonces, mi revisión sugerida de su función llamada es:

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

Este código no detecta errores de asignación de memoria, y probablemente se bloquea (pero si no, pierde memoria) si falla realloc (). Consulte el libro de Steve Maguire "Escribir código sólido" para una extensa discusión sobre los problemas de gestión de la memoria.

Respondida el 21/10/2008 a las 03:41
fuente por usuario Jonathan Leffler

votos
6

Solo un tiro en la oscuridad porque aún no lo he probado, pero cuando realloc devuelve el puntero de forma muy similar a malloc. Como realloc puede mover el puntero si es necesario, lo más probable es que esté funcionando con un puntero no válido si no hace lo siguiente:

input = realloc(input, strlen(input) + delta);
Respondida el 04/08/2008 a las 12:14
fuente por usuario John Downey

votos
4

Tenga en cuenta que intente editar su código para deshacerse de los códigos de escape html.

Bueno, aunque ha pasado un tiempo desde que usé C / C ++, el realloc que crece solo reutiliza el valor del puntero de memoria si hay espacio en la memoria después del bloque original.

Por ejemplo, considera esto:

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

Si su puntero apunta a la primera x, y. significa la ubicación de la memoria libre, y usted aumenta el tamaño de la memoria apuntada por su variable en 5 bytes, tendrá éxito. Esto es, por supuesto, un ejemplo simplificado ya que los bloques se redondean a un cierto tamaño para la alineación, pero de todos modos.

Sin embargo, si posteriormente intenta crecer por otros 10 bytes, y solo hay 5 disponibles, deberá mover el bloque en la memoria y actualizar su puntero.

Sin embargo, en su ejemplo está pasando la función un puntero al carácter, no un puntero a su variable, y así, mientras que la función strrep internamente podría ser capaz de ajustar la variable en uso, es una variable local para la función strrep y su código de llamada se quedará con el valor de la variable del puntero original.

Sin embargo, este valor del puntero se ha liberado.

En tu caso, la entrada es la culpable.

Sin embargo, haría otra sugerencia. En su caso, parece que la variable de entrada es de hecho entrada, y si lo es, no debe modificarse en absoluto.

Intentaré encontrar otra manera de hacer lo que quieres hacer, sin cambiar la información , ya que los efectos secundarios como este pueden ser difíciles de rastrear.

Respondida el 04/08/2008 a las 12:17
fuente por usuario Lasse Vågsæther Karlsen

votos
3

realloc es extraño, complicado y sólo debe utilizarse cuando se trata de una gran cantidad de porciones de memoria de veces por segundo. es decir, - donde realmente hace que su código más rápido.

He visto código donde

realloc(bytes, smallerSize);

fue utilizado y han trabajado para cambiar el tamaño de la memoria intermedia, por lo que es más pequeño. Trabajado sobre un millón de veces, y luego por alguna razón realloc decidió que, incluso si se acortamiento de la memoria intermedia, se le daría una buena nueva copia. Así te estrellas en un lugar al azar medio segundo después de lo malo que pasó.

Utilice siempre el valor de retorno de realloc.

Respondida el 16/05/2011 a las 11:57
fuente por usuario Tom Andersen

votos
3

Esto parece funcionar;

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

Suspiro, ¿hay alguna forma de publicar el código sin que sea una succión?

Respondida el 04/08/2008 a las 12:39
fuente por usuario Mark

votos
0

Mis consejos rápidos.

En lugar de:
void strrep(char *input, char *search, char *replace)
intentar:
void strrep(char *&input, char *search, char *replace)

y que en el cuerpo
input = realloc(input, strlen(input) + delta);

Generalmente lee sobre pasar argumentos de funciones como valores / referencia y realloc () descripción :).

Respondida el 04/08/2008 a las 12:20
fuente por usuario Piotr Tyburski