¿Memoria libre asignada en una función diferente?

5 minutos de lectura

avatar de usuario
jeff tratner

Estoy tratando de aprender C y actualmente estoy tratando de escribir una estructura de datos de pila básica, pero parece que no puedo obtener información básica malloc/free Correcto.

Aquí está el código que he estado usando (solo estoy publicando una pequeña parte aquí para ilustrar un problema específico, no el código total, pero el mensaje de error se generó simplemente ejecutando este código de ejemplo en valgrind)

#include <stdio.h>
#include <stdlib.h>

typedef struct Entry {
    struct Entry *previous;
    int value;
} Entry;

void destroyEntry(Entry entry);

int main(int argc, char *argv[])
{
    Entry* apple;
    apple = malloc(sizeof(Entry));
    destroyEntry(*(apple));
    return 0;
}

void destroyEntry(Entry entry)
{
    Entry *entry_ptr = &entry;
    free(entry_ptr);
    return;
}

Cuando lo ejecuto valgrind con --leak-check=full --track-origins=yesObtuve el siguiente error:

==20674== Invalid free() / delete / delete[] / realloc()
==20674==    at 0x4028E58: free (vg_replace_malloc.c:427)
==20674==    by 0x80485B2: destroyEntry (testing.c:53)
==20674==    by 0x8048477: main (testing.c:26)
==20674==  Address 0xbecc0070 is on thread 1's stack

Creo que este error significa que el destroyEntry No se permite que la función modifique la memoria asignada explícitamente en main. ¿Está bien? ¿Por qué no puedo simplemente free la memoria que asigné en main en otra función? (¿y es este comportamiento de alguna manera específico de main?)

  • +1 para pregunta clara y SSCCE.

    – Mateo Italia

    17 de junio de 2012 a las 12:19

  • @MatteoItalia Nunca había oído hablar de SSCCE antes de. Definitivamente un buen concepto. Gracias por presentármelo.

    –Jeff Tratner

    17 de junio de 2012 a las 12:27

Cada vez que pasa un parámetro a una función, se hace una copia y la función trabaja en esa copia. Entonces, en su caso, está tratando de free una copia del objeto original, que no tiene ningún sentido.

Debe modificar su función para tomar un puntero, y luego puede hacer que llame free directamente en ese puntero.

  • Para que quede claro, pasando *(apple) en el DestroyEntry función crea una nueva Entry estructura que está separada de la original apple ¿una?

    – usuario124384

    8 de febrero de 2019 a las 20:41

avatar de usuario
Liho

Esto está pasando por valor, lo que significa que se crea una copia, por lo que intenta liberar la memoria, donde la variable local entry reside Tenga en cuenta que entry es un objeto con duración de almacenamiento automático y la memoria donde reside se liberará automáticamente cuando su programa salga del alcance de destroyEntry función.

void destroyEntry(Entry entry)
{
    Entry *entry_ptr = &entry;
    free(entry_ptr);
    return;
}

Su función debe tomar un puntero (pasando por referencia):

void destroyEntry(Entry *entry)
{
    free(entry);
}

Entonces en lugar de destroyEntry(*(apple)); solo llamas destroyEntry(apple);. Tenga en cuenta que si no hay otra funcionalidad conectada con destroyEntry función, es redundante y es mejor simplemente llamar directamente free(apple).

avatar de usuario
gkimsey

Las otras respuestas aquí señalan el problema principal: porque elimina la referencia de su manzana cuando llama a destroyEntry en main(), pasa por referencia, creando una copia.

Incluso una vez que conoce su problema, es útil volver al error e intentar conectar el texto de lo que está viendo con el problema, de modo que la próxima vez que surja sea más probable que lo resuelva rápidamente. Encuentro que los errores de C y C++ pueden parecer enloquecedoramente ambiguos a veces.

En general, cuando tengo problemas para liberar punteros o eliminar objetos, me gusta imprimir direcciones, especialmente cuando las asigno y cuando trato de liberarlas. valgrind ya te dio la dirección del puntero malo, pero ayuda compararlo con uno bueno.

int main()
{
  Entry * apple;
  apple = malloc(sizeof(Entry));
  printf("apple's address = %p", apple);  // Prints the address of 'apple'
  free(apple);   // You know this will work
}

Después de hacer eso, notará que la declaración printf() le dio una dirección similar a 0x8024712 (simplemente inventando una dirección en el rango general correcto), pero su salida de valgrind dio 0x4028E58. Te darás cuenta de que están en dos lugares muy diferentes (de hecho, “0x4…” está en la pila, no en el montón desde donde se asigna malloc(), pero asumo que si recién estás comenzando eso es no es una bandera roja para usted todavía), por lo que sabe que está tratando de liberar memoria del lugar equivocado, por lo tanto, “libre no válido ()”.

Entonces, a partir de ahí, puede decirse a sí mismo “Está bien, de alguna manera mi puntero se está corrompiendo”. Ya redujo su problema a un pequeño ejemplo compilable, por lo que no le llevará mucho tiempo resolverlo desde allí.

TL; DR: cuando se encuentre con errores relacionados con el puntero, intente imprimir las direcciones o encontrarlas en su depurador favorito. A menudo, al menos, te indica la dirección correcta.

Nada de esto es para desalentar la publicación de su pregunta en Stack Exchange, por supuesto. Cientos de programadores probablemente se beneficiarán de haberlo hecho.

  • Gracias por publicar esto… es un buen punto sobre cómo descubrir errores con punteros y demás. He estado aprendiendo sobre stack vs. heap, pero no me di cuenta de que tenían direcciones de memoria generalizadas. (asi que, NULL –> 0x0, stack–>0x4 y el montón es algo más?).

    –Jeff Tratner

    20 de junio de 2012 a las 17:05

  • Además, no puedo hacer un cambio de un carácter en su publicación, pero en la cadena printf: "apple's address = %d" la cadena de formato debería ser realmente %pno %d¿Correcto?

    –Jeff Tratner

    20 de junio de 2012 a las 17:06

  • No puedo decirle con 100 % de certeza que las direcciones de montón siempre serán ‘0x8…’ y las direcciones de pila siempre serán ‘0x4…’. Para ser honesto, en estos días hago principalmente programación integrada donde el espacio de direcciones está muy bien definido y su proceso es el único que se ejecuta. Sin embargo, tengo entendido que en x86, cada proceso tiene su propio espacio de direcciones virtuales y, según mi experiencia, esas “reglas” parecen ser ciertas. Gracias por señalar el problema %d. Funciona (en el sentido de que imprime la dirección), pero es más difícil de leer. Usualmente uso “0x%x” porque así es como estoy acostumbrado a verlo.

    – gkimsey

    25 de junio de 2012 a las 14:36

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad