Punteros inteligentes/gestión de memoria segura para C?
memory pointers (8)
Yo, y creo que muchos otros, hemos tenido un gran éxito al usar punteros inteligentes para concluir operaciones inseguras de memoria en C ++, usando cosas como RAII, etcétera. Sin embargo, la administración de la memoria de embalaje es más fácil de implementar cuando tiene destructores, clases, sobrecarga del operador, etc.
Para alguien que escribe en C99 sin formato, ¿a dónde podría apuntar (sin juego de palabras) para ayudar con la gestión segura de la memoria?
Gracias.
Es difícil manejar punteros inteligentes en C sin procesar, ya que no tiene la sintaxis del lenguaje para hacer una copia de seguridad del uso. La mayoría de los intentos que he visto realmente no funcionan, ya que no tienes las ventajas de que los destructores se ejecuten cuando los objetos abandonan el alcance, que es realmente lo que hace que los punteros inteligentes funcionen.
Si realmente está preocupado por esto, tal vez desee considerar usar directamente un recolector de basura , y pasar por alto el requisito del puntero inteligente por completo.
La pregunta es un poco antigua, pero pensé que me tomaría el tiempo para vincular mi biblioteca de punteros inteligentes para compiladores de GNU (GCC, Clang, ICC, MinGW, ...).
Esta implementación se basa en el atributo de la variable de limpieza, una extensión de GNU, para liberar automáticamente la memoria cuando se sale del alcance, y como tal, no es ISO C99, sino C99 con extensiones de GNU.
Ejemplo:
simple1.c:
#include <stdio.h>
#include <csptr/smart_ptr.h>
int main(void) {
smart int *some_int = unique_ptr(int, 1);
printf("%p = %d/n", some_int, *some_int);
// some_int is destroyed here
return 0;
}
Recopilación y sesión Valgrind:
$ gcc -std=gnu99 -o simple1 simple1.c -lcsptr
$ valgrind ./simple1
==3407== Memcheck, a memory error detector
==3407== Copyright (C) 2002-2013, and GNU GPL/'d, by Julian Seward et al.
==3407== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==3407== Command: ./simple1
==3407==
0x53db068 = 1
==3407==
==3407== HEAP SUMMARY:
==3407== in use at exit: 0 bytes in 0 blocks
==3407== total heap usage: 1 allocs, 1 frees, 48 bytes allocated
==3407==
==3407== All heap blocks were freed -- no leaks are possible
==3407==
==3407== For counts of detected and suppressed errors, rerun with: -v
==3407== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Las herramientas de análisis de código estático como splint o Gimpel PC-Lint pueden ayudar aquí; incluso puede hacer que sean moderadamente "preventivas" conectándolas a su servidor de compilación automático de estilo de "integración continua". (Usted tiene uno de esos, ¿verdad?: Sonrisa :)
Hay otras variantes (algunas más caras) también en este tema ...
No puede hacer punteros inteligentes en C porque no proporciona la sintaxis necesaria, pero puede evitar fugas con la práctica. Escriba el código de liberación del recurso inmediatamente después de que lo asignó. Por lo tanto, cada vez que escriba un malloc
, debe escribir el correspondiente free
inmediatamente en una sección de limpieza.
En CI veo mucho el patrón ''GOTO cleanup'':
int foo()
{
int *resource = malloc(1000);
int retVal = 0;
//...
if (time_to_exit())
{
retVal = 123;
goto cleanup;
}
cleanup:
free(resource);
return retVal;
}
En C también usamos muchos contextos que asignan cosas, también se puede aplicar la misma regla para eso:
int initializeStuff(Stuff *stuff)
{
stuff->resource = malloc(sizeof(Resource));
if (!stuff->resource)
{
return -1; ///< Fail.
}
return 0; ///< Success.
}
void cleanupStuff(Stuff *stuff)
{
free(stuff->resource);
}
Esto es análogo a los constructores y destructores de objetos. Siempre que no regale los recursos asignados a otros objetos, no se filtrará y los punteros no colgarán.
No es difícil escribir un asignador personalizado que atexit
asignaciones y escriba los bloques con fugas atexit
.
Si necesita regalar punteros a los recursos asignados, puede crear contextos de contenedor para él y cada objeto posee un contexto de contenedor en lugar del recurso. Estas envolturas comparten el recurso y un objeto contador, que rastrea el uso y libera los objetos cuando nadie lo usa. Así es como funcionan las shared_ptr
y weak_ptr
C ++ 11. Está escrito con más detalle aquí: ¿cómo funciona weak_ptr?
Otro enfoque que quizás desee considerar es el enfoque de memoria agrupada que utiliza Apache . Esto funciona excepcionalmente bien si tiene un uso de memoria dinámica que está asociado con una solicitud u otro objeto efímero. Puede crear un grupo en su estructura de solicitud y asegurarse de que siempre asigne memoria del grupo y luego libere el grupo cuando termine de procesar la solicitud. No suena tan poderoso como lo es una vez que lo has usado un poco. Es casi tan bueno como RAII.
Puede definir macros, por ejemplo, BEGIN y END, para usar en lugar de llaves y desencadenar la destrucción automática de recursos que están saliendo de su alcance. Esto requiere que todos los recursos estén apuntados por punteros inteligentes que también contengan un puntero al destructor del objeto. En mi implementación, conservo una pila de punteros inteligentes en la memoria del montón, memorizo el puntero de la pila al ingresar a un alcance y llamo a los destructores de todos los recursos por encima del puntero de pila memorizado en la salida del alcance (END o reemplazo macro para el retorno). Esto funciona bien incluso si se usa el mecanismo de excepción setjmp / longjmp, y limpia todos los ámbitos intermedios entre el catch-block y el alcance donde se lanzó la excepción. Consulte https://github.com/psevon/exceptions-and-raii-in-c.git para la implementación.
Si está codificando en Win32, es posible que pueda usar el manejo estructurado de excepciones para lograr algo similar. Podrías hacer algo como esto:
foo() {
myType pFoo = 0;
__try
{
pFoo = malloc(sizeof myType);
// do some stuff
}
__finally
{
free pFoo;
}
}
Si bien no es tan fácil como RAII, puede recopilar todo su código de limpieza en un solo lugar y garantizar que se ejecute.
Sometimes i use this approach and it seems good :)
Object *construct(type arg, ...){
Object *__local = malloc(sizeof(Object));
if(!__local)
return NULL;
__local->prop_a = arg;
/* blah blah */
} // constructor
void destruct(Object *__this){
if(__this->prop_a)free(this->prop_a);
if(__this->prop_b)free(this->prop_b);
} // destructor
Object *o = __construct(200);
if(o != NULL)
;;
// use
destruct(o);
/*
done !
*/