2009-04-06 14 views
12

Creo que tengo una buena comprensión de cómo manejar la memoria en C++, pero hacerlo en C es diferente, estoy un poco apagado.¿La mejor manera de manejar la asignación de memoria en C?

En C++ tengo constructores y destructores, tengo la novedad bastante simple y elimino y sé cómo encapsularlo usando RAII, usando punteros inteligentes y dentro de las clases.

Sin embargo, en C No puedo manejar malloc y liberarme de la misma manera. No sé cómo ocultarlos y cómo automatizar las cosas. Todo lo que puedo entender es usar funciones para iniciar y destruir mis punteros. ¿Pero cómo debería estructurar mi manejo de la memoria?

Al escribir esto me he dado cuenta de que esto es más una cuestión acerca de que yo entienda el flujo de C que cualquier otra cosa, sino una pregunta a la vez.

Editar: Gracias por las respuestas, pero tengo que reformularme.

Cuando digo que uso RAII y punteros inteligentes para C++, no quiero lo mismo para C, sé que no es lo mismo. Pero cómo manejo la asignación de memoria en C++ está conectado a estas técnicas.

Por ejemplo, en mis clases, agrego y destruyo dinámicamente la memoria que usa mi clase. De esta forma puedo lograr una especie de encapsulado, no necesito saber cuándo/cómo/por qué la clase maneja su memoria, simplemente lo hace. Esto significa que puedo "esconder" el menor manejo de memoria y solo enfocarme en algunas clases "más grandes".

Lo que quiero saber es cuál es la mejor práctica en el manejo de la memoria en C? No hay clases con constructores/destructores para manejar esto por mí. ¿Es bueno asignar memoria al comienzo de una función o usar una función que la cree para mí? ¿Y cómo debería liberarlos de nuevo?

Estas son preguntas amplias y difieren de una situación a otra, pero ¿cómo prefiere manejarlo? ¿Qué consejos y lecciones puedes dar?

Respuesta

16

Parte de la confusión es que es inherentemente más difícil en C. malloc y free son similares a new y delete: malloc asigna memoria nueva y devuelve un puntero a esa memoria. free hace que la memoria esté disponible de nuevo, siempre que su memoria se haya asignado utilizando malloc. De lo contrario, solo hace hash de algún pedazo de memoria. No le importa

Lo importante con malloc/free es decidir y mantener sistemáticamente un uso disciplinado. Éstos son algunos consejos:

SIEMPRE verifique el puntero de regresar de malloc para NULL

if((p = (char *) malloc(BUFSIZ)) == NULL { 
    /* then malloc failed do some error processing. */ 
} 

Por cinturón y tirantes de seguridad, establezca un puntero a NULL después de liberarla.

free(p); 
p = NULL ; 

tratar de malloc y libre un trozo de memoria dentro del mismo ámbito, si es posible:

{ char * p ; 
    if((p = malloc(BUFSIZ)) == NULL { 
     /* then malloc failed do some error processing. */ 
    } 

/* do your work. */ 

    /* now you're done, free the memory */ 

    free(p); 
    p = NULL ; /* belt-and suspenders */ 
} 

Cuando no se puede, dejar claro que lo que usted está volviendo es malloc 'memoria ed , por lo que la persona que llama puede liberarlo.

/* foo: do something good, returning ptr to malloc memory */ 
char * foo(int bar) { 
    return (char *) malloc(bar); 
} 
+0

No necesita el molde (excepto cuando pasa directamente una llamada malloc() como un argumento de función varargs o usando compiladores ANSI previos). Eso en realidad está induciendo malos habits porque oculta un #include faltante . – dirkgently

+0

+1 para malloc/free en el mismo ámbito y para p = NULL después de free(). malloc/free en el mismo ámbito es una buena localización, y tenga en cuenta que "haga su trabajo" puede incluir llamadas a funciones que toman punteros. La mejor respuesta de lejos en este momento. – dwc

+0

eso es otra cosa estilística, Dirk. Prefiero tener el elenco explícito, porque eso es parte de mi propio razonamiento de tipo que paso mientras escribo C. Veo "(char *) malloc" como un funcionamiento abstracto que devuelve un trozo de memoria malloc'ed. –

10

Mientras escribía este me he dado cuenta esta es más una cuestión de mí entender el flujo de C que cualquier otra cosa, pero una pregunta a la vez .

honestamente creo que usted debe leer sobre K&R si no es así.

+0

¿Por qué la gente todavía sugiere ese libro antiguo? Claro que es notable en la historia de C, escrito por sus autores, pero su última publicación tiene 20 años, no cubre ISO C99 e incluso menos prácticas actuales, como OO y memoria inteligente proporcionada por bibliotecas como GLib (de GNOME). – Juliano

+4

@Juliano: No veo ningún problema en recomendar un libro antiguo para aprender un idioma antiguo. _El C Lenguaje de programación_ está notablemente bien escrito, y la gente lo sigue recomendando porque hace un buen trabajo al enseñar exactamente lo que se pretende. Las prácticas supuestamente populares y las bibliotecas específicas de terceros están fuera del alcance de un libro sobre el idioma en sí. C99 no rompe nada significativo, y sus nuevas características no son bien compatibles con muchos compiladores de todos modos. Si está tratando de aprender C (y no las prácticas de GLib o OO o ...) realmente no hay un mejor libro de texto. –

2

Bueno, en C tienes que hacer toda la gestión de memoria manualmente como ya has descubierto. Eso no debería ser una sorpresa.

6

La triste realidad es que C no está diseñado para encapsular todos esos problemas de administración de memoria.

Si observa API de bastante alta calidad como POSIX, verá que el patrón común es que pasa un puntero a un puntero a una función, que luego asigna la memoria y luego la vuelve a pasar a una función que lo destruye.

No es necesariamente elegante, pero no creo que hay muchas maneras de hacer lo que realmente elegante sin simulación de programación orientada a objetos en C

1

que no sé cómo ocultar ellos y cómo automatizar cosas .

C y C++ son diferentes idiomas. Ahora, dilo cien veces a ti mismo. Sé fuerte.

¿Qué quieres decir con ocultar? ¿A qué te refieres con automatizar? ¿Puedes publicar algunos ejemplos? ¿Por qué necesitas ocultar y/o automatizar?

buenos lugares en línea para comenzar con la asignación de memoria C son:

+0

Por alguna extraña razón, su segundo enlace no funciona: http://doc.cat-v.org/henry_spencer/ten-commandments – Klelky

+0

@Klelky: Tenía razón. Se arregló el enlace. – dirkgently

0

La forma más habitual es

MyType *ptr = malloc(array_size * sizeof *ptr); 

Pero si quieres ser compatible con C++, haz

MyType *ptr = (MyType*) malloc(array_size * sizeof *ptr); 

También se puede hacer una macro

#define MALLOC(NUMBER, TYPE) (TYPE *) malloc(NUMBER * sizeof(TYPE)) 
MyType *ptr = MALLOC(10, MyType); 

Por supuesto, sin RAII, asegúrese de que en algún momento posterior usted tiene

free(ptr); 
-2

Usted puede pensar que lo que digo es extraño, pero no hay una gran diferencia entre C++ y C C tiene RAII también. RAII no pertenece solo a C++.

Lo único que debe tener suficiente disciplina para hacer la gestión.

clase C++:

class foo { 
public: 
    ofstream ff; 
    int x,y; 
    foo(int _x) : x(_x),y(_x){ ff.open("log.txt"); } 
    void bar() { ff<<x+y<<endl; } 
}; 

int main() 
{ 
    auto_ptr<foo> f(new foo); 
    f->bar(); 
} 

C objeto

typedef struct FOO { 
    FILE *ff; 
    int x,y; 
} *foo_t; 

foo_t foo_init(int x) 
{ 
    foo_t p=NULL; 
    p=malloc(sizeof(struct FOO)); // RAII 
    if(!p) goto error_exit; 
    p->x=x; p->y=x; 
    p->ff=fopen("log.txt","w"); // RAII 
    if(!p->ff) goto error_exit; 
    return p; 
error_exit: // ON THROW 
    if(p) free(p); 
    return NULL; 
} 

void foo_close(foo_t p) 
{ 
    if(p) fclose(p->ff); 
    free(p); 
} 

void foo_bar(foo_t p) 
{ 
    fprintf(p->ff,"%d\n",p->x+p->y); 
} 

int main() 
{ 
    foo_t f=foo_init(1); 
    if(!f) return 1; 
    foo_bar(f); 
    foo_close(f); 
    return 0; 
} 
+0

Esto no es RAII. RAII está vinculando recursos a objetos en la pila, utilizando el destructor para manejar la liberación del recurso. C no tiene semántica de constructor/destructor. Además, este código es más ineficiente que C++ porque prueba errores, mientras que C++ los trata como excepcionales. – rlbond

+0

Hay dos puntos: a) C no tiene excepciones, son condiciones "si". b) RAII no se trata de desructores, sino de la inicialización adecuada de los recursos y su liberación. Se puede hacer fácilmente en C sobre la pila cuando se divide el código en 3 partes: inicialización, trabajo y finalización. – Artyom

1

Una forma I "ocultar" la asignación de memoria y de asignación de DE es hacerlo pasar a envases personalizados. Pase el contenedor por un objeto sin mallar. Deje que se preocupe por malloc y cuando elimine el objeto, deje que se preocupe por lo gratis. Por supuesto, esto solo funciona si solo está almacenando un objeto en un contenedor. Si tengo referencias de objetos por todo el lugar Voy a crear el equivalente de los métodos constructor y el destructor con la sintaxis de C:

glob* newGlob(); 
void freeGlob(glob* g); 

(por objeto me refiero a todo lo que apuntaría a - no ++ objetos c).

7

Lamentablemente, existen estrategias limitadas para automatizar la asignación de memoria y la desasignación en C. El compilador de C++ genera mucho código detrás de escena para usted: realiza un seguimiento de cada variable en la pila y se asegura de que el destructor apropiado llamado cuando se limpia la pila. En realidad, se trata de un tipo de generación de código bastante sofisticado, especialmente cuando se lanzan excepciones a la mezcla.

C por otro lado es mucho más simple, por lo que a veces se denomina "lenguaje ensamblador de alto nivel". C no tiene ningún mecanismo que garantice que se llame a un bit de código cuando sale una función o se saca una variable de la pila, por lo que depende de usted hacer un seguimiento de cada bit de memoria que asigna y cada archivo o red socket que se abre y limpiarlos en el punto apropiado. No hay una forma práctica de construir un puntero inteligente automático en C.

Un concepto que debe considerar es "grupos de memoria". Básicamente, en lugar de intentar realizar un seguimiento de cada bloque individual de memoria que asigna, crea un grupo, hace un poco de trabajo, coloca cada bloque de memoria que asigna en el grupo, luego libera todo el grupo cuando termina. Intercambias un poco de rendimiento y control aquí para facilitar la carga cognitiva del programador, pero la mayoría de las veces vale la pena.

Debe echar un vistazo al proyecto Apache Portable Runtime. Tienen una biblioteca de grupo de memoria (documentos en http://apr.apache.org/docs/apr/1.3/group__apr__pools.html). Si la APR es demasiado para que usted se zambulla, puede implementar un grupo de memoria muy simple utilizando tres funciones y una estructura de datos de lista vinculada. Pseudocódigo sería algo así como:

struct Pool { 
    void* memoryBlock; 
    struct Pool *next; 
} 

struct Pool *createPool(void) { 
    /* allocate a Pool and return it */ 
} 

void addToPool(struct Pool *pool, void *memoryBlock) { 
    /* create a new Pool node and push it onto the list */ 
} 

void destroyPool(struct Pool *pool) { 
    /* walk the list, free each memory block then free its node */ 
} 

El uso de la piscina es algo como esto:

int main(void) { 
    struct Pool *pool = createPool(); 
    /* pool is empty */ 

    doSomething(pool); 

    /* pool full of crap, clean it up and make a new one */ 
    destroyPool(pool); 
    pool = createPool(); 
    /* new pool is empty */ 

    doMoreStuff(pool); 
    destroyPool(pool); 

    return 0; 
} 
0

No estoy muy seguro de lo que estás pidiendo, pero C es bastante sencillo:

struct Foo *f0 = malloc(sizeof(*f)); // alloc uninitialized Foo struct 
struct Foo *f1 = calloc(1,sizeof(*f)); // alloc Foo struct cleared to all zeroes 

//You usually either want to clear your structs using calloc on allocation, or memset. If 
// you need a constructor, just write a function: 
Foo *Foo_Create(int a, char *b) 
{ 
    Foo *r = calloc(1,sizeof(*r)); 
    r->a = a; 
    r->b = strdup(b); 
    return r; 
} 

Here is a simple C workflow with arrays: 
struct Foo **foos = NULL; 
int n_foos = 0; 
... 
for(i = 0; i < n_foos; ++i) 
{ 
    struct Foo *f = calloc(1,sizeof(*f)); 
    foos = realloc(foos,sizeof(*foos)*++n_foos); // foos before and after may be different 
    foos[n_foos-1] = f; 
} 

Si obtiene de fantasía, puede escribir macros para ayudar:

#define MALLOCP(P) calloc(1,sizeof(*P)) // calloc inits alloc'd mem to zero 

Un par de puntos:

  • malloc, calloc, realloc, etc. todo el uso libre(), por lo que la gestión de estas cosas es fácil. Solo sé consistente.
  • rendimiento para mallocs puede sea lento. Alguien publicó un enlace sobre esto arriba.En estos días, las asignaciones rápidas de múltiples subprocesos son clave, ver tcmalloc et al. Probablemente no tengas que preocuparte por esto.
  • en una arquitectura de memoria virtual moderna, malloc casi nunca fallará a menos que esté fuera del espacio de direcciones virtuales. Si esto sucede, cambie a 64 bits;)
  • Asegúrese de estar utilizando un sistema que tenga comprobación de límites, valores limpios, seguimiento de fugas y todas esas cosas buenas (consulte valgrind, pila de depuración win32, etc.)
1

Hay muchas cosas que puede hacer para hacer su vida más fácil. Parece que ya has acertado con la idea de crear fábricas/constructores para tus objetos-C. Ese es un buen comienzo de seguimiento.

Algunas otras ideas a considerar.

  1. no se conforme con el estándar malloc/free. vaya a buscar uno mejor que haya sido abierto o escriba uno que combine el uso de la memoria de los objetos que está creando. Además, estamos hablando de C, va a sobrescribir sus objetos libres una y otra vez y olvidará liberar algunos, así que construya algún soporte de depuración en su malloc. escribir el suyo no es difícil si no puede encontrar uno que satisfaga sus necesidades.

  2. usan más de un montón. use uno por clase de objeto que cree, use montones temporales si sabe que va a tener una gran cantidad de objetos transitorios que están relacionados, esto mantiene la fragmentación de la memoria y le permite administrar la memoria de acuerdo con el uso.

  3. vistazo a estrategias como piscinas Objective-C de

  4. si cree que entiende cómo C++ funciona, a continuación, añadir el comportamiento constructor para la asignación de memoria en una factoría de objetos no es tan difícil de hacer y utilizando una costumbre construido gratuita a continuación, puede proporcionar la capacidad de llamar a un destructor en el objeto que se le da free'd recuperar parte de la conducta de C++ te gustó

0

sé que esto es una entrada antigua, pero en realidad no ha sido gran parte de una respuesta completa a las mejores prácticas en términos de estilo, que creo que es lo que el operador realmente quería, así que aquí está mi opinión sobre la asignación de memoria en C. Nota: Soy más una persona de C++, gran parte de mi pensamiento proviene de esa actitud.

A menudo es útil saber si su puntero está asignado, así que siempre asigne NULL a un puntero cuando lo declare. También puede crearse una función segura y gratuita que libera la memoria y luego le asigna NULL para que no tenga que preocuparse.

Si asigna memoria en un archivo C, entonces debe liberarlo en el mismo archivo. Esto es quizás más restrictivo de lo necesario, sin embargo, si está escribiendo una biblioteca, definitivamente debe liberar cualquier memoria dentro de su biblioteca que esté mallocada en su biblioteca. Esto se debe a que en Windows dlls tiene un montón diferente al exe, por lo que mallocing memory en un dll y liberarlo en el exe corrompe su montón.

Por extensión y en aras de la simetría, esto significa que si tiene una función que devuelve un puntero a la memoria asignada, entonces debe tener una función que libere esa memoria. Esta es la razón por la cual muchas bibliotecas tienen una función de inicialización que devuelve un puntero a algunos datos (generalmente emitidos como un vacío *) y luego una función de limpieza que liberará los recursos de la biblioteca. Si puede realizar malloc y liberarlo dentro de la misma función, eso es bueno ya que le facilita el seguimiento de las cosas.

No intente asignar toda su memoria al comienzo de una función y luego libérela al final. Esto solo significa que si desea volver a la mitad de la función, debe liberar toda la memoria, mientras que si realiza malloc y libera memoria mientras avanza, tendrá menos punteros para liberar.

Si a menudo tiene funciones que asignan muchos punteros, considere la posibilidad de crear y una matriz que contenga punteros a todos sus punteros al comienzo de la función, y luego tener una función que los libere a todos. Esto le ahorrará el inevitable síndrome "Volveré y ordenaré mi memoria se filtrará más tarde" si desea regresar a la función intermedia.

El concepto de fábricas es útil. Una fábrica sería una función que malloc la memoria de una estructura, asigna un puntero a la estructura, inicializa sus variables y luego le devuelve el puntero. Si el primero de ellos fue un destructor o una matriz de funciones específicas, entonces puede tener una función de destrucción genérica que puede llamar al destructor de cualquier estructura, y luego liberar la memoria de la estructura. También puede ocultar algunos de los detalles internos de la clase al tener una definición de estructura diferente hacia adentro y hacia afuera. COM se basa en estos principios.

Así que estas son solo las formas en que veo la memoria en C. No es tan elegante como en C++, pero como confías en los humanos para lidiar con ella, hay estrategias como las anteriores que pueden hacer cosas como lo más simple posible para ellos.

Tenga en cuenta también que siempre hay excepciones para cada regla; estas son solo cosas en las que pienso cuando uso C. Estoy seguro de que otras personas tienen otras ideas.

Phil

Cuestiones relacionadas