cuándo liberar puntero en C y cómo saber si está liberado

Soy nuevo en C, tratando de averiguar acerca de la asignación de memoria en C que me confundió un poco

#include  #include  typedef struct { int a; } struct1_t; int main() { funct1(); //init pointer return 1; } int funct2(struct1_t *ptr2struct) { printf("print a is %d\n",ptr2struct->a); //free(ptr2struct); printf("value of ptr in funct2 is %p\n", ptr2struct); return 1; //success } int funct1(){ struct1_t *ptr2struct = NULL; ptr2struct = malloc(sizeof(*ptr2struct)); ptr2struct->a = 5; printf("value of ptr before used is %p", ptr2struct); if (funct2(ptr2struct) == 0) { goto error; } free(ptr2struct); printf("value of ptr in funct1 after freed is is %p\n", ptr2struct); return 1; error: if(ptr2struct) free(ptr2struct); return 0; } 

Tengo funct 1 que llama funct 2, y después de usar el puntero asignado en funct1, trato de liberar el puntero. Y creo un caso en el que si el valor de retorno en funct2 no es 1, vuelva a intentar liberar el puntero.

Mi pregunta esta abajo

qué práctica es mejor, si debo liberar la memoria en funct2 (después de pasarla) o en funct1 (después de que termine de obtener el valor de retorno de funct1) La segunda cosa es si esto es correcto para hacer un error de Goto, y error:

 if(ptr2struct) free(ptr2struct); 

Mi tercera pregunta es, ¿cómo verifico si el valor asignado ya está liberado o no? porque después de obtener el valor de retorno, libero el puntero, pero si lo imprimo, muestra la misma ubicación con el asignado (así que no es un puntero nulo).

1) ¿Debo liberarlo en la función de llamada o en la función de llamada?

Trato de hacer la liberación en la misma función que hace el maltrato. Esto mantiene las preocupaciones de administración de memoria en un solo lugar y también proporciona una mejor separación de preocupaciones, ya que la función llamada en este caso también puede funcionar con punteros que no han sido malloc-ed o usar el mismo puntero dos veces (si desea hacerlo). ).

2) ¿Es correcto hacer un “goto error”?

¡Sí! Al saltar a un solo lugar al final de la función, evita tener que duplicar el código de liberación de recursos. Este es un patrón común y no es tan malo ya que el “goto” solo sirve como una especie de statement de “retorno” y no está haciendo ninguna de sus cosas realmente difíciles y malvadas por las que es más conocido.

 //in the middle of the function, whenever you would have a return statement // instead do return_value = something; goto DONE; //... DONE: //resorce management code all in one spot free(stuff); return return_value; 

C ++, por otro lado, tiene una forma ordenada de hacer este tipo de gestión de recursos. Dado que los destructores se denominan de forma determinista justo antes de que salga una función, se pueden usar para empaquetar de forma ordenada a este rey de la gestión de recursos. Ellos llaman a esta técnica RAII

Otra forma en que otros idiomas tienen que lidiar con esto es finalmente bloquear.

3) ¿Puedo ver si un puntero ya ha sido liberado?

Lamentablemente, no puedes. Lo que algunas personas hacen es establecer el valor de la variable del puntero en NULL después de liberarlo. No duele (ya que su antiguo valor no debería usarse después de ser liberado de todos modos) y tiene la propiedad agradable de que la liberación de un puntero nulo se especifica como no operativa.

Sin embargo, hacerlo no es infalible. Tenga cuidado de tener otras variables alias del mismo puntero, ya que aún contendrán el valor antiguo, que ahora es un puntero peligroso.

Llamar a free () en un puntero no lo cambia, solo marca la memoria como libre. Su puntero seguirá apuntando a la misma ubicación que contendrá el mismo valor, pero ese valor ahora puede sobrescribirse en cualquier momento, por lo que nunca debe usar un puntero una vez liberado. Para asegurarse de ello, es una buena idea establecer siempre el puntero en NULL después de liberarlo.

Mi pregunta esta abajo

qué práctica es mejor, si debo liberar la memoria en funct2 (después de pasarla) o en funct1 (después de que termine de obtener el valor de retorno de funct1)

Esta es una pregunta de “propiedad”. Quién posee la memoria asignada. Normalmente, esto debe decidirse en función del diseño de su progtwig. Por ejemplo, el único propósito de func1 () podría ser asignar solo memoria. Es decir, en su implementación, func1 () es la función para la asignación de memoria y luego la función “llamar” usa la memoria. En ese caso, la propiedad para liberar la memoria es con la persona que llama func1 y NO con func1 ().

Lo segundo es si esto es correcto para cometer un error de goto, y error: el uso de “goto” generalmente es mal visto. Causa desorden en el código que podría evitarse fácilmente. Sin embargo, digo “en general”. Hay casos en los que goto puede ser silencioso, práctico y útil. Por ejemplo, en sistemas grandes, la configuración del sistema es un gran paso. Ahora, imagine que llama a una única función Config () para el sistema que asigna memoria para sus diferentes estructuras de datos en diferentes puntos de la función como

  config() { ...some config code... if ( a specific feature is enabled) { f1 = allocateMemory(); level = 1; } ....some more code.... if ( another feature is enabled) { f2 = allocateMemory(); level = 2; } ....some more codee.... if ( another feature is enabled) { f3 = allocateMemor(); level =3; } /*some error happens */ goto level_3; level_3: free(f3); level_2: free(f2); level_1: free(f1); } 

En este caso, puede usar goto y, de forma elegante, solo libera la cantidad de memoria asignada hasta el momento en que falló la configuración.

Sin embargo, basta con decir que en su ejemplo goto es fácilmente evitable y debe evitarse.

Mi tercera pregunta es, ¿cómo verifico si el valor asignado ya está liberado o no? porque después de obtener el valor de retorno, libero el puntero, pero si lo imprimo, muestra la misma ubicación con el asignado (así que no es un puntero nulo).

Fácil. Establece la memoria liberada como NULL. La otra ventaja, aparte de la mencionada por MK, es que al pasar el puntero NULL al modo libre se generará un NOP, es decir, no se realiza ninguna operación. Esto también te ayudará a evitar cualquier problema de doble eliminación.

Lo que estoy a punto de compartir son mis propias prácticas de desarrollo en C. No son la única manera de organizarse. Solo estoy delineando un camino no el camino.

De acuerdo, en muchos sentidos, “C” es un lenguaje vago, por lo que mucha disciplina y rigor proviene de uno mismo como desarrollador. He estado desarrollando en “C” por más de 20 años profesionalmente, rara vez he tenido que arreglar cualquier software de grado de producción que he desarrollado. Si bien un poco del éxito puede atribuirse a la experiencia, una buena parte de ello está arraigada en la práctica constante.

Sigo un conjunto de prácticas de desarrollo, que son bastante extensas, y me ocupo de todo, tan trivial como las tabs para nombrar las convenciones y lo que no. Me limitaré a lo que hago con respecto a las estructuras en general y, en particular, a la administración de memoria.

  • Si tengo una estructura que se utiliza en todo el software, escribo crear / destruir; Funciones de tipo init / done para ello:

      struct foo * init_foo(); void done_foo(struct foo *); 

    y asignar y desasignar la estructura en estas funciones.

  • Si manipulo los elementos de una estructura directamente en todo el progtwig, entonces no lo teclee. Me tomo la molestia de usar la palabra clave struct en cada statement para que sepa que es una estructura. Esto es suficiente cuando el umbral del dolor NO es tanto como para que me moleste. 🙂

  • Si encuentro que la estructura está actuando MUY como un objeto, elijo manipular ESTRICTAMENTE los elementos de la estructura a través de una API opaca; luego defino su interfaz a través de las funciones set / get type para cada elemento, creo una ‘statement de reenvío’ en el archivo de encabezado utilizado por cada otra parte del progtwig, creo un typedef opaco al puntero de la estructura y solo declaro La estructura real en el archivo de implementación de la API de estructura.

foo.h:

 struct foo; typedef struct foo foo_t; void set_e1(foo_t f, int e1); int get_ei(foo_t f); int set_buf(foo_t f, const char *buf); char * get_buf_byref(foo_t f) char * get_buf_byval(foo_t f, char *dest, size_t *dlen); 

foo.c:

 #include  struct foo { int e1; char *buf; ... }; void set_e1(foo_t f, int e1) { f->e1 = e1; } int get_ei(foo_t f) { return f->e1; } void set_buf(foo_t f, const char *buf) { if ( f->buf ) free ( f->buf ); f->buf = strdup(buf); } char *get_buf_byref(foo_t f) { return f->buf; } char *get_buf_byval(foo_t f, char **dest, size_t *dlen) { *dlen = snprintf(*dest, (*dlen) - 1, "%s", f->buf); /* copy at most dlen-1 bytes */ return *dest; } 
  • Si las estructuras relacionadas son muy complicadas, es posible que incluso desee implementar punteros de función directamente en una estructura base y luego proporcionar manipuladores reales en extensiones particulares de esa estructura.

Verá una gran similitud entre el enfoque que he descrito anteriormente y la progtwigción orientada a objetos. Se supone que es eso …

Si mantiene sus interfaces limpias de esta manera, no importa si tiene que establecer variables de instancia en NULL en todo el lugar. El código, con suerte, se reducirá a una estructura más estrecha donde los errores tontos son menos probables.

Espero que esto ayude.

Sé que esto está respondido, pero quería dar mi opinión. Por lo que yo entiendo, cuando llama a una función con parámetros como aquí (el puntero), los parámetros se insertan en la stack (FILO) .


Por lo tanto, el puntero pasado a la función se sacará automáticamente de la stack pero no liberará el puntero en funct1 () . Por lo tanto, necesitaría liberar el puntero en funct1 () Corríjame si estoy equivocado.