2008-08-24 10 views
76

Siempre escuché que en C tiene que ver realmente cómo administra la memoria. Y todavía estoy empezando a aprender C, pero hasta ahora, no he tenido que hacer ningún tipo de gestión de actividades relacionadas ... Siempre imaginé tener que liberar variables y hacer todo tipo de cosas feas. Pero este no parece ser el caso.C Administración de memoria

¿Alguien me puede mostrar (con ejemplos de código) un ejemplo de cuándo tendría que hacer una "gestión de memoria"?

+0

Un buen lugar para aprender [G4G] (http://www.geeksforgeeks.org/memory-layout-of-c-program/) – EsmaeelE

Respuesta

208

Hay dos lugares donde las variables se pueden poner en la memoria. Cuando se crea una variable de la siguiente manera:

int a; 
char c; 
char d[16]; 

Las variables se crean en la "pila ". Las variables de pila se liberan automáticamente cuando salen del alcance (es decir, cuando el código ya no puede alcanzarlas). Es posible que los escuche denominados variables "automáticas", pero eso ha pasado de moda.

Muchos ejemplos para principiantes usarán solo variables de pila.

La pila es agradable porque es automática, pero también tiene dos inconvenientes: (1) El compilador necesita saber de antemano qué tan grandes son las variables, y (b) el espacio de la pila es algo limitado. Por ejemplo: en Windows, bajo la configuración predeterminada para el enlazador de Microsoft, la pila está configurada en 1 MB, y no todo está disponible para sus variables.

Si no sabe en tiempo de compilación qué tan grande es su matriz, o si necesita una gran matriz o estructura, necesita el "plan B".

Plan B se denomina "montón". Por lo general, puede crear variables tan grandes como le permita el sistema operativo, pero debe hacerlo usted mismo. publicaciones anteriores que mostraron una forma que puede hacerlo, aunque hay otras maneras:

int size; 
// ... 
// Set size to some value, based on information available at run-time. Then: 
// ... 
char *p = (char *)malloc(size); 

(Tenga en cuenta que las variables en el montón no son manipulados directamente, sino a través de punteros)

Una vez que crean una variable del montón , el problema es que el compilador no puede decir cuándo terminaste con él, por lo que pierdes la liberación automática. Ahí es donde entra en juego la "liberación manual" a la que se refería. Su código ahora es responsable de decidir cuándo ya no se necesita la variable y liberarla para que la memoria pueda tomarse para otros fines.Para el caso anterior, con:

free(p); 

Lo que hace que esta segunda opción "negocio sucio" es que no siempre es fácil saber cuando la variable no se necesita más. Olvidarse de lanzar una variable cuando no la necesite hará que su programa consuma más memoria que la necesita. Esta situación se llama "fuga". La memoria "filtrada" no puede utilizarse para nada hasta que el programa finalice y el sistema operativo recupere todos sus recursos. Incluso problemas más desagradables son posibles si libera una variable de montón por error antes de que realmente ha terminado con ella.

En C y C++, usted es responsable de limpiar las variables del montón como se muestra arriba. Sin embargo, hay idiomas y entornos como Java y lenguajes .NET como C# que usan un enfoque diferente, donde el montón se limpia por sí mismo. Este segundo método, llamado "recolección de basura", es mucho más fácil para el desarrollador, pero usted paga una multa por gastos generales y rendimiento. Es un equilibrio.

(I han pasado por alto muchos detalles para dar un simple, pero es de esperar respuesta más nivelado)

+3

Si usted quiere poner algo en la pila, pero no sabe lo grande que es en tiempo de compilación, alloca() puede ampliar el marco de pila para hacer espacio. No hay freea(), el marco de pila completo aparece cuando la función regresa. Usando alloca() para grandes asignaciones está lleno de peligros. – DGentry

+8

Esta publicación ayuda a los recién llegados a comprender los principios fundamentales. Gracias. +1 –

+4

Euro, tengo que decir que es una de las mejores respuestas que he leído en SO - ¡buen trabajo! 1 de mí también;) – Rob

8

Tiene que hacer "gestión de la memoria" cuando quiere usar la memoria en el montón en lugar de la pila. Si no sabes qué tan grande es hacer una matriz hasta el tiempo de ejecución, entonces debes usar el montón. Por ejemplo, es posible que desee almacenar algo en una cadena, pero no sabe qué tan grande será su contenido hasta que se ejecute el programa. En ese caso, escribiría algo como esto:

char *string = malloc(stringlength); // stringlength is the number of bytes to allocate 

// Do something with the string... 

free(string); // Free the allocated memory 
-2

Sure. Si crea un objeto que existe fuera del alcance, lo usa. Aquí hay un ejemplo artificial (tenga en cuenta que mi sintaxis estará desactivada; mi C está oxidada, pero este ejemplo todavía ilustrará el concepto):

class MyClass 
{ 
    SomeOtherClass *myObject; 

    public MyClass() 
    { 
     //The object is created when the class is constructed 
     myObject = (SomeOtherClass*)malloc(sizeof(myObject)); 
    } 

    public ~MyClass() 
    { 
     //The class is destructed 
     //If you don't free the object here, you leak memory 
     free(myObject); 
    } 

    public void SomeMemberFunction() 
    { 
     //Some use of the object 
     myObject->SomeOperation(); 
    } 


}; 

En este ejemplo, estoy usando un objeto de tipo SomeOtherClass durante la vida útil de MyClass. El objeto SomeOtherClass se usa en varias funciones, por lo que he asignado dinámicamente la memoria: el objeto SomeOtherClass se crea cuando se crea MyClass, se usa varias veces durante la vida del objeto y luego se libera una vez que se libera MyClass.

Obviamente si esto fuera código real, no habría ninguna razón (aparte del posible consumo de memoria de la pila) para crear myObject de esta manera, pero este tipo de creación/destrucción de objetos se vuelve útil cuando hay muchos objetos, y desea controlar con precisión cuándo se crean y se destruyen (para que su aplicación no absorba 1GB de RAM durante toda su vida útil, por ejemplo), y en un entorno con ventana, esto es prácticamente obligatorio, como objetos que usted crea (botones, por ejemplo), necesitan existir bien fuera del alcance de cualquier función en particular (o incluso de clase).

+0

C no tiene clases :) – Zxaos

+1

je, sí, eso es C++ ISN' ¿Es eso? Es increíble que hayan tardado cinco meses para que alguien me llame. – TheSmurf

15

Aquí hay un ejemplo. Suponga que tiene un strdup() que duplica una cadena:

char *strdup(char *src) 
{ 
    char * dest; 
    dest = malloc(strlen(src) + 1); 
    if (dest == NULL) 
     abort(); 
    strcpy(dest, src); 
    return dest; 
} 

Y se llama así:

main() 
{ 
    char *s; 
    s = strdup("hello"); 
    printf("%s\n", s); 
    s = strdup("world"); 
    printf("%s\n", s); 
} 

se puede ver que el programa funciona, pero tiene memoria asignada (a través de malloc) sin liberarlo. Ha perdido su puntero al primer bloque de memoria cuando llamó a strdup por segunda vez.

Esto no es un problema importante para este pequeña cantidad de memoria, pero hay que considerar el caso:

for (i = 0; i < 1000000000; ++i) /* billion times */ 
    s = strdup("hello world"); /* 11 bytes */ 

Ha utilizado ahora hasta 11 GB de memoria (posiblemente más, dependiendo de su administrador de memoria) y si no ha bloqueado su proceso probablemente se está ejecutando muy lentamente.

Para solucionar, es necesario llamar a free() por todo lo que se obtiene con malloc() después de terminar de usarlo:

s = strdup("hello"); 
free(s); /* now not leaking memory! */ 
s = strdup("world"); 
... 

Esperanza este ejemplo ayuda!

+0

Me gusta esta respuesta mejor. Pero tengo una pequeña pregunta secundaria. Me esperaba algo como esto que hay que resolver con las bibliotecas, no hay una biblioteca que imitan los tipos de datos básicos y añadir funciones de memoria liberando a ellos de modo que cuando se acostumbran las variables que también obtener liberados de forma automática? – Lorenzo

+0

Ninguno que sea parte del estándar. Si ingresas a C++, obtienes cadenas y contenedores que administran automáticamente la memoria. –

+0

Ya veo, ¿entonces hay algunas librerías de terceros? ¿Podrías nombrarlos? – Lorenzo

1

También es posible que desee utilizar la asignación de memoria dinámica cuando necesite definir una matriz enorme, digamos int [10000]. No puedes simplemente ponerlo en pila porque entonces, hm ...Obtendrás un desbordamiento de pila.

Otro buen ejemplo sería una implementación de una estructura de datos, por ejemplo, una lista vinculada o un árbol binario. No tengo un código de muestra para pegar aquí, pero puedes buscarlo en Google fácilmente.

2

Aquí hay algunas buenas respuestas acerca de cómo asignar y liberar memoria, y en mi opinión el lado más desafiante de usar C es asegurar que la única memoria que utiliza es la memoria que ha asignado, si esto no se hace correctamente, con lo que termina es el primo de este sitio, un desbordamiento del búfer, y es posible que sobrescriba la memoria que está utilizando otra aplicación, con resultados muy impredecibles.

Un ejemplo:

int main() { 
    char* myString = (char*)malloc(5*sizeof(char)); 
    myString = "abcd"; 
} 

En este punto se ha asignado 5 bytes para miCadena y lo llenó de "ABCD \ 0" (cuerdas terminan en un nulo - \ 0). Si su asignación de cuerdas era

myString = "abcde"; 

usted sería la asignación de "abcde" en los 5 bytes que haya tenido asignados a su programa, y ​​el carácter nulo de salida se pondría al final de esto - una parte de memoria que no ha sido asignada para su uso y podría ser gratuita, pero podría ser utilizada por otra aplicación: esta es la parte crítica de la administración de la memoria, donde un error tendrá consecuencias imprevisibles (ya veces irrepetibles).

+0

Aquí asigna 5 bytes. Suelto al asignar un puntero. Cualquier intento de liberar este puntero conduce a un comportamiento indefinido. Nota C-Strings no sobrecarga el operador = no hay copia. –

+0

Aunque, realmente depende del malloc que está utilizando. Muchos operadores malloc se alinean a 8 bytes. Entonces, si este malloc usa un sistema de encabezado/pie de página, malloc reservaría 5 + 4 * 2 (4 bytes tanto para el encabezado como para el pie de página). Eso sería 13 bytes, y malloc solo le daría 3 bytes adicionales para la alineación. No digo que sea una buena idea usar esto, porque solo funcionará con sistemas cuyo malloc funciona así, pero al menos es importante saber por qué podría funcionar algo malo. – kodai

+0

Loki: He editado la respuesta a usar 'strcpy()' en lugar de '' =; Supongo que fue la intención de Chris B-C. – echristopherson

1

(estoy escribiendo porque siento las respuestas hasta el momento no son del todo en la marca.)

La razón por la que tiene que recordar la administración de la memoria es cuando tiene un problema/solución que requiere la creación de estructuras complejas. (Si tus programas fallan si asignas mucho espacio en la pila a la vez, eso es un error.) Típicamente, la primera estructura de datos que necesitarás aprender es alguna clase de list. Aquí hay uno solo vinculado, de la parte superior de mi cabeza:

typedef struct listelem { struct listelem *next; void *data;} listelem; 

listelem * create(void * data) 
{ 
    listelem *p = calloc(1, sizeof(listelem)); 
    if(p) p->data = data; 
    return p; 
} 

listelem * delete(listelem * p) 
{ 
    listelem next = p->next; 
    free(p); 
    return next; 
} 

void deleteall(listelem * p) 
{ 
    while(p) p = delete(p); 
} 

void foreach(listelem * p, void (*fun)(void *data)) 
{ 
    for(; p != NULL; p = p->next) fun(p->data); 
} 

listelem * merge(listelem *p, listelem *q) 
{ 
    while(p != NULL && p->next != NULL) p = p->next; 
    if(p) { 
    p->next = q; 
    return p; 
    } else 
    return q; 
} 

Naturalmente, que le gustaría algunas otras funciones, pero básicamente, esto es lo que necesita para la gestión de memoria. Debo señalar que hay una serie de trucos que son posibles con la gestión "manual" de memoria, por ejemplo,

  • Usando el hecho de que malloc está garantizado (por el lenguaje estándar) para devolver un puntero divisible por 4,
  • asignación de espacio adicional para algún propósito siniestro de su cuenta,
  • crear memory pool s ..

Obtener un buen depurador ... buena suerte!

+0

El aprendizaje de estructuras de datos es el siguiente paso clave para comprender la gestión de la memoria. Aprender los algoritmos para ejecutar adecuadamente estas estructuras le mostrará los métodos apropiados para superar estos obstáculos. Esta es la razón por la cual encontrará Data-structures y Algorithms enseñados en los mismos cursos. –

0

@Ted Percival:
... que no es necesario para emitir malloc() 's valor de retorno.

Tienes razón, por supuesto. Creo que eso siempre ha sido cierto, aunque no tengo una copia de K&R para verificar.

No me gustan muchas de las conversiones implícitas en C, por lo que tiendo a usar moldes para hacer que la "magia" sea más visible. A veces ayuda a la legibilidad, a veces no, y a veces hace que el compilador capture un error silencioso. Aún así, no tengo una opinión fuerte sobre esto, de una forma u otra.

Esto es especialmente probable si su compilador entiende los comentarios al estilo C++.

Sí ... me atrapaste allí. Paso mucho más tiempo en C++ que en C. Gracias por notar eso.

+0

@echristopherson, gracias. Tiene usted razón, pero tenga en cuenta que esta Q/A fue desde agosto de 2008, antes de que Stack Overflow estuviera incluso en público Beta. En aquel entonces, aún estábamos averiguando cómo debería funcionar el sitio. El formato de esta Pregunta/Respuesta no debe verse necesariamente como un modelo de cómo usar SO. ¡Gracias! –

+0

Ah, gracias por señalar eso. No me había dado cuenta de que ese aspecto del sitio todavía estaba en proceso de cambio. – echristopherson

0

@Euro Micelli

Un aspecto negativo es que añadir punteros a la pila ya no son válidas cuando se devuelve la función, por lo que no puede devolver un puntero a una variable de pila de una función. Este es un error común y una razón importante por la que no puede funcionar con solo las variables de la pila. Si su función necesita devolver un puntero, entonces tiene que malloc y manejar la administración de la memoria.

0

En C, en realidad tiene dos opciones diferentes. Primero, puede dejar que el sistema administre la memoria por usted. Alternativamente, puedes hacer eso por ti mismo. En general, le conviene apegarse al primero el mayor tiempo posible. Sin embargo, la memoria administrada automáticamente en C es extremadamente limitada y deberá administrarla manualmente en muchos casos, como por ejemplo:

a. Desea que la variable sobreviva a las funciones y no desea tener una variable global. ex:

 
struct pair{ 
    int val; 
    struct pair *next; 
} 

struct pair* new_pair(int val){ 
    struct pair* np = malloc(sizeof(struct pair)); 
    np->val = val; 
    np->next = NULL; 
    return np; 
} 

b. desea tener memoria asignada dinámicamente. El ejemplo más común es una matriz sin longitud fija:

 
int *my_special_array; 
my_special_array = malloc(sizeof(int) * number_of_element); 
for(i=0; i 

c. You want to do something REALLY dirty. For example, I would want a struct to represent many kind of data and I don't like union (union looks soooo messy):

struct data{ int data_type; long data_in_mem; }; struct animal{/*something*/}; struct person{/*some other thing*/}; struct animal* read_animal(); struct person* read_person(); /*In main*/ struct data sample; sampe.data_type = input_type; switch(input_type){ case DATA_PERSON: sample.data_in_mem = read_person(); break; case DATA_ANIMAL: sample.data_in_mem = read_animal(); default: printf("Oh hoh! I warn you, that again and I will seg fault your OS"); }

Ver, un valor largo es suficiente para contener ALGO. Solo recuerda liberarlo o te arrepentirás. Este es uno de mis trucos favoritos para divertirse en C: D.

Sin embargo, en general, usted querrá mantenerse alejado de sus trucos favoritos (T___T). Usted romperá su sistema operativo, tarde o temprano, si los usa con demasiada frecuencia. Siempre y cuando no use * alloc y free, es seguro decir que todavía es virgen, y que el código todavía se ve bien.

+0

"Ver, un valor largo es suficiente para mantener CUALQUIER COSA" -:/de qué estás hablando, en la mayoría de los sistemas un valor largo es de 4 bytes, exactamente lo mismo que un int. La única razón por la que se ajusta a los punteros aquí es porque el tamaño del largo pasa a ser el mismo que el del puntero. Sin embargo, en realidad deberías estar usando void *. –

4

Creo que la forma más concisa de responder a la pregunta es considerar el papel del puntero en C. El puntero es un mecanismo ligero pero potente que le brinda una inmensa libertad a costa de la inmensa capacidad de dispararse en el pie .

En C, la responsabilidad de asegurar que sus punteros apuntan a la memoria que posee es suya y solo suya. Esto requiere un enfoque organizado y disciplinado, a menos que abandonas punteros, lo que hace que sea difícil escribir efectiva C.

Las respuestas publicadas hasta la fecha concentrado en automático (pila) y las asignaciones variables montón. El uso de la asignación de pila hace que la memoria sea manejada de manera automática y conveniente, pero en algunas circunstancias (búferes grandes, algoritmos recursivos) puede llevar al horrendo problema del desbordamiento de la pila. Saber exactamente cuánta memoria puede asignar en la pila depende mucho del sistema. En algunos escenarios incrustados, algunas decenas de bytes pueden ser su límite, en algunos escenarios de escritorio puede usar megabytes de manera segura.

La asignación de montón es menos inherente al idioma. Básicamente es un conjunto de llamadas de biblioteca que le otorga la propiedad de un bloque de memoria de un tamaño dado hasta que esté listo para devolverlo ('liberarlo'). Parece simple, pero está asociado con un dolor indescriptible del programador. Los problemas son simples (liberar la misma memoria dos veces, o no [fugas de memoria], no asignar suficiente memoria [desbordamiento de búfer], etc.) pero es difícil de evitar y depurar. Un enfoque estrictamente disciplinado es absolutamente obligatorio en la práctica pero, por supuesto, el lenguaje en realidad no lo exige.

Me gustaría mencionar otro tipo de asignación de memoria que otras publicaciones han ignorado. Es posible asignar variables de forma estática al declararlas fuera de cualquier función.Creo que, en general, este tipo de asignación tiene una mala reputación porque es utilizada por variables globales. Sin embargo, no hay nada que diga que la única forma de usar la memoria asignada de esta manera es como una variable global indisciplinada en un lío de código de spaghetti. El método de asignación estática se puede usar simplemente para evitar algunos de los inconvenientes del montón y los métodos de asignación automática. Algunos programadores de C se sorprenden al saber que se han construido grandes y sofisticados programas C integrados y de juegos sin utilizar la asignación de heap en absoluto.

3

Recuerde que siempre inicializa los punteros a NULL, ya que un puntero no inicializado puede contener una dirección de memoria válida pseudoaleatoria que puede hacer que los errores del puntero avancen en silencio. Al hacer que un puntero se inicialice con NULL, siempre puede detectar si está utilizando este puntero sin inicializarlo. La razón es que los sistemas operativos "cablean" la dirección virtual 0x00000000 a excepciones de protección general para atrapar el uso de punteros nulos.

Cuestiones relacionadas