2009-04-28 19 views
34

Yo, y creo que muchos otros, hemos tenido un gran éxito al usar punteros inteligentes para concluir las operaciones de memoria inseguras 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.Punteros inteligentes/gestión de memoria segura para C?

Para alguien escribiendo en C99 sin formato, ¿a dónde podría apuntar (sin juego de palabras) para ayudar con la administración segura de la memoria?

Gracias.

Respuesta

12

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, quizás desee considerar usar directamente un garbage collector y pasar por alto el requisito del puntero inteligente por completo.

+0

¿Cómo funciona? ¿Cómo puede rastrear las asignaciones de puntero? – Calmarius

+0

@ Calmarius Hay varias formas en que funcionan. Ver: http://en.wikipedia.org/wiki/Garbage_collection_(computer_science) –

+0

Ya veo. Pregunté sobre el GC que vinculó. Afirma que funciona en programas C no modificados, reemplazando solo el malloc y el realloc. Pero, ¿cómo ubica los punteros que apuntan al bloque asignado? Se pueden copiar en el programa. – Calmarius

3

Herramientas de análisis de código estático como splint o Gimpel PC-Lint puede ayudar aquí; incluso puede hacer que sean moderadamente "preventivas" al conectarlas a su servidor automático de construcción de "integración continua". (Usted tiene uno de esos, derecha:? Muecas :)

Hay otra (un poco más caro) variantes sobre este tema también ...

+0

+1 Buena llamada en las herramientas de comprobación de código estático. –

2

Si está codificando en Win32 que podría ser capaz de utilizar structured exception handling para lograr algo similar. Se podría hacer algo como esto:

foo() { 
    myType pFoo = 0; 
    __try 
    { 
     pFoo = malloc(sizeof myType); 
     // do some stuff 
    } 
    __finally 
    { 
     free pFoo; 
    } 
} 

bien no es tan fácil como RAII, puede obtener la totalidad de su código de limpieza en un lugar y garantizar que se ejecuta.

8

Otro enfoque que quizás desee considerar es el enfoque de memoria agrupada que Apache uses. 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.

4

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 ver el patrón de 'limpieza GOTO' mucho:

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 utilizamos una gran cantidad de contextos, que atribuyen las cosas, la misma regla se puede aplicar para eso también:

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 rastree las asignaciones y escriba los bloques con fuga atexit.

Si necesita regalar punteros a los recursos asignados puede crear contextos de contenedor para ello 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 funciona C++ 11 shared_ptr y weak_ptr. Está escrito con más detalle aquí: How does weak_ptr work?

2

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. Ver https://github.com/psevon/exceptions-and-raii-in-c.git para la implementación.

12

La pregunta es un poco antigua, pero pensé que me tomaría el tiempo para vincular a mi smart pointer library para los compiladores de GNU (GCC, Clang, ICC, MinGW, ...).

Esta aplicación se basa en el atributo variable de la limpieza, una extensión de GNU, para liberar automáticamente la memoria al salir de su alcance, y como tal, es no ISO C99, C99, pero 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; 
} 

Compilación & Valgrind sesión:

$ 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) 
0
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 ! 
*/