tecnicas sistemas resueltos particiones operativos memoria gestion ejercicios asignacion administracion c memory

sistemas - C Gestión de memoria



particiones variables (12)

Siempre escuché que en C tienes que ver realmente cómo administras 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.

¿Puede alguien mostrarme (con ejemplos de código) un ejemplo de cuándo tendría que hacer algo de "gestión de memoria"?


@ Ted Percival :
... no es necesario arrojar el valor de retorno de malloc ().

Estás en lo cierto, 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 comentarios al estilo de C ++.

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


(Estoy escribiendo porque siento que las respuestas hasta ahora no están del todo claras).

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 se bloquean si asignas mucho espacio en la pila a la vez, eso es un error.) Por lo general, la primera estructura de datos que necesitarás aprender es algún tipo de list . Aquí hay un enlace único, fuera 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, le gustaría tener algunas otras funciones, pero básicamente, esto es para lo que necesita administración de memoria. Debo señalar que hay un número de trucos que son posibles con la administración de memoria "manual", por ejemplo,

  • Usar el hecho de que malloc está garantizado (por el estándar de lenguaje) para devolver un puntero divisible por 4,
  • asignando espacio extra para algún propósito siniestro propio,
  • creando grupo de memoria s ..

Consigue un buen depurador ... ¡ Buena suerte!


@ Euro Micelli

Una de las negativas para agregar es que los punteros a la pila ya no son válidos cuando la función regresa, por lo que no puede devolver un puntero a una variable apilada 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.


Aquí hay algunas buenas respuestas sobre cómo asignar y liberar memoria, y en mi opinión, el lado más desafiante del uso de C es garantizar que la única memoria que utiliza es la memoria que ha asignado; si esto no se hace correctamente, lo que finaliza es el primo de este sitio, un desbordamiento del búfer, y es posible que esté sobrescribiendo 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, ha asignado 5 bytes para myString y lo ha llenado con "abcd / 0" (las cadenas terminan en un valor nulo - / 0). Si su asignación de cadena fue

myString = "abcde";

Debería asignar "abcde" en los 5 bytes que tiene asignados a su programa, y ​​el carácter nulo final se colocaría al final de esta, una parte de la memoria que no ha sido asignada para su uso y podría ser gratis, pero igualmente podría ser utilizado por otra aplicación: esta es la parte crítica de la administración de la memoria, donde un error tendrá consecuencias impredecibles (ya veces irrepetibles).


Aquí hay un ejemplo. Supongamos que tiene una función 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 lo llamas así:

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

Puede ver que el programa funciona, pero ha asignado memoria (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 gran cosa para esta pequeña cantidad de memoria, pero considera el caso:

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

Ahora ha consumido 11 gigas de memoria (posiblemente más, dependiendo de su administrador de memoria) y si no se ha bloqueado, su proceso probablemente se ejecute con bastante lentitud.

Para solucionarlo, debe llamar a free () para todo lo que se obtiene con malloc () después de que termine de usarlo:

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

Espero que este ejemplo ayude!


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 liviano pero poderoso que le otorga una inmensa libertad a costa de la inmensa capacidad de dispararse en el pie.

En C, la responsabilidad de garantizar que sus indicadores apuntan a la memoria que posee es suya y solo suya. Esto requiere un enfoque organizado y disciplinado, a menos que abandones los indicadores, lo que dificulta escribir C.

Las respuestas publicadas hasta la fecha se concentran en las asignaciones automáticas (de pila) y de variables de 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 del montón es menos inherente al lenguaje. 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.


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 tendrá que 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; }

segundo. 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"); }

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

Sin embargo, en general, 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.


Hay dos lugares donde las variables se pueden poner en la memoria. Cuando creas una variable como esta:

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 buena 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".

El plan B se llama el " montón ". Por lo general, puede crear variables tan grandes como le permita el sistema operativo, pero debe hacerlo usted mismo. Publicaciones anteriores le mostraron una forma en que puede hacerlo, aunque hay otras formas:

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 se manipulan directamente, sino a través de punteros)

Una vez que crea una variable de montón, el problema es que el compilador no puede saber cuándo ha terminado con ella, por lo que pierde 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 sea "desagradable negocio" es que no siempre es fácil saber cuándo la variable ya no es necesaria. 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 sueltas una variable de montón por error antes de que hayas terminado con ella.

En C y C ++, usted es responsable de limpiar sus variables de pila 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.

(He pasado por alto muchos detalles para dar una respuesta más simple, pero afortunadamente más nivelada)


Por supuesto. Si creas un objeto que existe fuera del alcance, lo usas. Aquí hay un ejemplo artificial (ten 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 este 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 tienes muchos objetos, y quieres controlar con precisión cuando son creados y destruidos (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 más o menos 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).


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 ponerlo en la pila porque entonces, hm ... obtendrás un desbordamiento de la 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.


Tienes que hacer "gestión de memoria" cuando quieras 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ías 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


Una cosa para recordar es inicializar siempre 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.