listas - pilas en c++ ejercicios resueltos




Liberación automática de variables de pila en C (2)

Desafortunadamente, en C no hay punteros inteligentes ... ¿pero es posible construir una macro que envuelva la declaración de la variable y llamar a la función de invocación con esa variable como una variable de entrada al salir del ámbito donde se declaró la variable?

Perdón por la frase larga, pero estoy trabajando en kernel xnu donde tienes muchos elementos que tienen contadores de referencia incorporados, y no hay que olvidar no reafiar este elemento cuando termines de usarlo para evitar pérdidas de memoria.

Por ejemplo, si tengo el siguiente tipo de proc_t :

struct proc;
typedef struct proc * proc_t;

Quiero declarar una variable de pila basada en este tipo dentro de un ámbito, por ejemplo:

{
    proc_t_release_upon_exit proc_t proc_iter = proc_find(mypid);
    //the rest of the code in this scope 
}

Después de que el preprocesador analice la macro y antes de la compilación, el siguiente código que espero que se genere es:

{ 
    proc_t myproc = proc_find(mypid)
    //the rest of the code in scope
    proc_rele(myproc);
}

¿Hay alguna manera de definir dicha macro como en C?


C proporciona una forma de colocar el código sintácticamente antes de otro código que se ejecutará primero: el bloque for . Recuerde que la cláusula 3 de una estructura for puede contener una expresión arbitraria, y siempre se ejecuta después de la ejecución del bloque principal.

Por lo tanto, puede crear una macro que realice una llamada predeterminada después de un fragmento dado del código siguiente al envolver un bloque for en una macro:

#define M_GEN_DONE_FLAG() _done_ ## __LINE__ 

#define M_AROUND_BLOCK2(FLAG, DECL, BEFORE, AFTER) \
  for (int FLAG = (BEFORE, 0); !FLAG; ) \
    for (DECL; !FLAG; FLAG = (AFTER, 1))

#define M_AROUND_BLOCK(DECL, BEFORE, AFTER) M_AROUND_BLOCK2(M_GEN_DONE_FLAG(), DECL, BEFORE, AFTER)

#define M_CLEANUP_VAR(DECL, CLEANUP_CALL) M_AROUND_BLOCK(DECL, (void)0, CLEANUP_CALL)

... y puedes usarlo así:

#include <stdio.h>

struct proc;
typedef struct proc * proc_t;

proc_t proc_find(int);
void proc_rele(proc_t);

void fun(int mypid) {
  M_CLEANUP_VAR (proc_t myproc = proc_find(mypid), proc_rele(myproc))
  {
    printf("%p\n", &myproc); // just to prove it's in scope
  }
}

El truco aquí es que un bloque for acepta una declaración siguiente, pero si no ponemos esa declaración en la definición de macro, podemos seguir la macro invocación con un bloque de código normal y este pertenecerá "mágicamente" a nuestro nuevo ámbito -sintaxis de estructura de control, simplemente en virtud de seguir el expandido for .

Cualquier optimizador que valga la pena usar eliminará el indicador de bucle en su configuración de optimización más baja. Tenga en cuenta que el nombre que choca con la bandera no es una gran preocupación (es decir, no necesita un gensym para esto) porque la bandera tiene el alcance del cuerpo del bucle, y cualquier bucle anidado lo ocultará con seguridad si usa el mismo nombre de bandera .

La ventaja aquí es que el alcance de la variable a la limpieza está restringido (no se puede usar fuera del compuesto inmediatamente después de su declaración) y visualmente explícito (debido a dicho compuesto).

Pros:

  • esto es estándar C sin extensiones
  • el flujo de control es directo
  • en realidad es (de alguna manera) menos detallado que __attribute__ __cleanup__

Contras:

  • no proporciona una RAII "completa" (es decir, no protege contra las excepciones goto o C ++: __cleanup__ generalmente se implementa con maquinaria C ++ debajo del capó, por lo que es más completo). Más en serio, no protege contra el return anticipado (gracias @Voo). (Al menos puede protegerse contra un break fuera de lugar - si lo desea - al agregar una tercera línea, switch (0) default: hasta el final de M_AROUND_BLOCK2 ).
  • no todos están de acuerdo con las macros que extienden la sintaxis (pero ten en cuenta que estás ampliando la semántica de C aquí, así que ...)

Puede usar el atributo variable de limpieza en GCC. Por favor, eche un vistazo a esto: http://echorand.me/site/notes/articles/c_cleanup/cleanup_attribute_c.html

Código de muestra:

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

void free_memory(void **ptr)
{
    printf("Free memory: %p\n", *ptr);
    free(*ptr);
}

int main(void)
{
    // Define variable and allocate 1 byte, the memory will be free at
    // the end of the scope by the free_memory function. The free_memory 
    // function will get the pointer to the variable *ptr (double pointer
    // **ptr).
    void *ptr  __attribute__ ((__cleanup__(free_memory))) = malloc(1);
    return 0;
}

Si guarda el código fuente en un archivo llamado main.c, puede compilarlo con este comando:

gcc main.c -o main

y verifica si hay pérdidas de memoria al:

valgrind ./main

Ejemplo de salida de valgrind:

==1026== Memcheck, a memory error detector
==1026== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==1026== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==1026== Command: ./main
==1026== 
Free memory: 0x51ff040
==1026== 
==1026== HEAP SUMMARY:
==1026==     in use at exit: 0 bytes in 0 blocks
==1026==   total heap usage: 1 allocs, 1 frees, 1 bytes allocated
==1026== 
==1026== All heap blocks were freed -- no leaks are possible
==1026== 
==1026== For counts of detected and suppressed errors, rerun with: -v
==1026== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)






macros