Pregunta

Intenté escribir una función de reemplazo de cadenas en C, que funciona en un char *, que ha sido asignado utilizando malloc().Es un poco diferente en que buscará y reemplazará cadenas, en lugar de caracteres en la cadena inicial.

Es trivial hacerlo 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 suficiente espacio asignado.Si trato de usar realloc(), me sale un error que me dice que estoy haciendo un doble libre - cosa que no veo como estoy, ya que solo estoy usando realloc().

Quizás 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 realloc() en un caso en el que la cadena reemplazada será más larga que la cadena inicial.(Todavía funciona, simplemente muestra errores además del 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");
    }
}
¿Fue útil?

Solución

Como regla general, debes nunca realice una liberación o reasignación 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 utilizar ninguna de las funciones de asignación en un búfer de usuario.

Siempre que ahora no pueda realizar 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 búfer lo suficientemente largo para esta. que se produzca el reemplazo.

Luego, podría crear otra función para realizar reemplazos múltiples, pero tendrá que asignar todo el espacio para la cadena resultante y copiar la cadena ingresada por el 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);

Otros consejos

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

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

Para responder al OP, realloc() devuelve un puntero a la memoria recién reasignada.El valor de retorno debe almacenarse en algún lugar.Generalmente, 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, no pueden cambiar el valor del puntero que se pasa como entrada a esta función.Puedes asignar lo que quieras, pero el cambio quedará fuera de alcance al final de la función.En el siguiente bloque, "entrada" puede o no ser un puntero no válido una vez que se completa la función:

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 solucionar este problema devolviendo el nuevo puntero como salida de la función.Si hace eso, la persona que llama tiene la responsabilidad de no volver a utilizar nunca más el puntero que utilizó para la entrada.Si coincide con el valor de retorno, entonces tiene dos punteros al 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.Desreferenciarlo podría provocar un error de segmentación.

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

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 podría no ser válido ahora.

Creo que la solución más limpia aquí es evitar el uso de realloc() al intentar modificar la entrada de la función que llama.Simplemente malloc() un nuevo buffer, devuélvalo y deje que la persona que llama decida si libera o no el texto anterior.¡Esto tiene el beneficio adicional de permitir que la persona que llama conserve la cadena original!

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

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

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

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

Antes de realizar el análisis, estaré de acuerdo con quienes dicen que su interfaz no es tan estelar;sin embargo, si solucionó los problemas de pérdida/pisoteamiento de memoria y documentó el requisito de "se debe asignar memoria", podría estar "bien".

¿Cuáles son los problemas?Bueno, le pasas un búfer a realloc(), y realloc() te devuelve un nuevo puntero al área que debes usar, e ignoras ese valor de retorno.En consecuencia, realloc() probablemente haya liberado la memoria original, y luego le pasa 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 pierde memoria, sino que significa que continúas usando el espacio original, y el disparo en la oscuridad de John Downey señala que estás haciendo un mal uso de realloc(), pero no enfatiza cuán severamente lo estás haciendo.También hay un error uno por uno 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 informarle a la persona que llama sobre el último valor de la cadena.Debido a que siguió pisoteando la cadena original más el espacio posterior, parece que el código funcionó, pero si su código de llamada liberó el espacio, también obtendría un error de doble liberación, o podría obtener un volcado de núcleo o equivalente porque la información de control de la memoria está completamente codificada.

Su código tampoco protege contra el crecimiento indefinido; considere reemplazar 'Noel' por 'Joyeux Noel'.Cada vez, agregarías 7 caracteres, pero encontrarías otro Noel en el texto reemplazado y lo expandirías, y así sucesivamente.Mi solución (a continuación) no soluciona este problema; la solución simple probablemente sea 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 que abordar.

Entonces, mi revisión sugerida de la 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 falla (pero si no, pierde memoria) si falla realloc().Consulte el libro 'Writing Solid Code' de Steve Maguire para obtener una discusión extensa sobre cuestiones de administración de memoria.

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++, la realloc que crece solo reutiliza el valor del puntero de memoria si hay espacio en la memoria después del bloque original.

Por ejemplo, considere esto:

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

Si su puntero apunta a la primera x y .significa ubicación de memoria libre, y aumenta el tamaño de la memoria señalada por su variable en 5 bytes, será exitoso.Por supuesto, este es un ejemplo simplificado, ya que los bloques se redondean hasta un cierto tamaño para su alineación, pero de todos modos.

Sin embargo, si posteriormente intentas aumentarlo otros 10 bytes y solo hay 5 disponibles, necesitarás mover el bloque en la memoria y actualizar tu puntero.

Sin embargo, en su ejemplo, está pasando a la función un puntero al carácter, no un puntero a su variable y, por lo tanto, aunque la función strrep internamente podría ajustar la variable en uso, es una variable local para la función strrep y su código de llamada quedará con el valor de la variable de puntero original.

Sin embargo, este valor de puntero se ha liberado.

En su caso, la entrada es la culpable.

Sin embargo, haría otra sugerencia.En tu caso se parece al aporte La variable es de hecho una entrada y, si lo es, no debe modificarse en absoluto.

Por tanto, intentaría encontrar otra forma de hacer lo que quieres hacer, sin cambiar aporte, ya que efectos secundarios como este pueden ser difíciles de detectar.

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 mierda?

realloc es extraño, complicado y sólo debe usarse cuando se trabaja con mucha memoria muchas veces por segundo.es decir.- donde realmente hace que tu código sea más rápido.

He visto código donde

realloc(bytes, smallerSize);

se usó y trabajó para cambiar el tamaño del búfer, haciéndolo más pequeño.Funcionó aproximadamente un millón de veces, luego, por alguna razón, realloc decidió que incluso si estuviera acortando el búfer, le daría una buena copia nueva.Entonces chocas en un lugar aleatorio 1/2 segundo después de que sucedieron las cosas malas.

Utilice siempre el valor de retorno de realloc.

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 lea sobre cómo pasar argumentos de función como valores/referencia y descripción de realloc() :).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top