Siempre he oído que en C tienes que vigilar realmente cómo gestionas la memoria. Y todavía estoy empezando a aprender C, pero hasta ahora, no he tenido que hacer ninguna actividad relacionada con la gestión de memoria. Siempre imaginé tener que liberar variables y hacer todo tipo de cosas feas. Pero este no parece ser el caso.
¿Alguien puede mostrarme (con ejemplos de código) un ejemplo de cuándo tendría que hacer algo de "administración de memoria"?
Respuestas:
Hay dos lugares donde las variables se pueden guardar en la memoria. Cuando creas una variable como esta:
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 las escuche llamar 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, en la configuración predeterminada del vinculador de Microsoft, la pila se establece en 1 MB y no todo está disponible para sus variables.
Si no sabe en el momento de la compilación qué tan grande es su matriz, o si necesita una matriz o estructura grande, necesita el "plan B".
El plan B se llama " 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:
(Tenga en cuenta que las variables en el montón no se manipulan directamente, sino mediante 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 la "liberación manual" a la que se refería. Su código ahora es responsable de decidir cuándo la variable ya no es necesaria y liberarla para que la memoria se pueda utilizar para otros fines. Para el caso anterior, con:
Lo que hace que esta segunda opción sea un "negocio desagradable" es que no siempre es fácil saber cuándo ya no se necesita la variable. Olvidar liberar una variable cuando no la necesita hará que su programa consuma más memoria de la que necesita. Esta situación se denomina "fuga". La memoria "filtrada" no se puede utilizar para nada hasta que el programa finalice y el sistema operativo recupere todos sus recursos. Son posibles problemas aún más desagradables si libera una variable de montón por error antes que haya terminado con ella.
En C y C ++, usted es responsable de limpiar sus variables de montón como se muestra arriba. Sin embargo, hay lenguajes y entornos como Java y lenguajes .NET como C # que utilizan un enfoque diferente, donde el montón se limpia por sí solo. Este segundo método, llamado "recolección de basura", es mucho más fácil para el desarrollador, pero paga una penalización en gastos generales y rendimiento. Es un equilibrio.
(He pasado por alto muchos detalles para dar una respuesta más simple, pero con suerte más nivelada)
fuente
malloc()
, su causa UB,(char *)malloc(size);
consulte stackoverflow.com/questions/605845/…He aquí un ejemplo. Suponga que tiene una función strdup () que duplica una cadena:
Y lo llamas así:
Puede ver que el programa funciona, pero ha asignado memoria (a través de malloc) sin liberarla. 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 considere el caso:
Ahora ha usado hasta 11 gigabytes de memoria (posiblemente más, dependiendo de su administrador de memoria) y si no se ha bloqueado, su proceso probablemente se esté ejecutando bastante lento.
Para solucionarlo, debe llamar a free () para todo lo que se obtiene con malloc () después de terminar de usarlo:
¡Espero que este ejemplo ayude!
fuente
Tienes que hacer "administración de memoria" cuando quieras usar memoria en el montón en lugar de en la pila. Si no sabe qué tan grande hacer una matriz hasta el tiempo de ejecución, entonces debe usar el montón. Por ejemplo, es posible que desee almacenar algo en una cadena, pero no sepa qué tan grande será su contenido hasta que se ejecute el programa. En ese caso, escribirías algo como esto:
fuente
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 brinda una inmensa libertad a costa de una inmensa capacidad para dispararse en el pie.
En C, la responsabilidad de asegurarse de que sus indicadores apunten a la memoria que posee es suya y solo suya. Esto requiere un enfoque organizado y disciplinado, a menos que se olvide de los consejos, lo que dificulta escribir una C.
Las respuestas publicadas hasta la fecha se concentran en asignaciones automáticas (pila) y variables de pila. El uso de la asignación de pila genera una memoria conveniente y administrada automáticamente, pero en algunas circunstancias (búferes grandes, algoritmos recursivos) puede conducir al horrendo problema del desbordamiento de pila. Saber exactamente cuánta memoria puede asignar en la pila depende en gran medida del sistema. En algunos escenarios integrados, unas pocas docenas 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. Es básicamente un conjunto de llamadas a la biblioteca que le otorga la propiedad de un bloque de memoria de un tamaño determinado hasta que esté listo para devolverlo ('liberarlo'). Suena simple, pero está asociado con un dolor incalculable del programador. Los problemas son simples (liberar la misma memoria dos veces, o nada en absoluto [pérdidas de memoria], no asignar suficiente memoria [desbordamiento del búfer], etc.) pero difíciles de evitar y depurar. Un enfoque sumamente disciplinado es absolutamente obligatorio en la práctica, pero, por supuesto, el idioma no lo exige.
Me gustaría mencionar otro tipo de asignación de memoria que otras publicaciones han ignorado. Es posible asignar variables estáticamente declarándolas fuera de cualquier función. Creo que, en general, este tipo de asignación tiene mala reputación porque lo utilizan 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 espagueti. El método de asignación estática se puede utilizar simplemente para evitar algunos de los errores del montón y los métodos de asignación automática. Algunos programadores de C se sorprenden al saber que los grandes y sofisticados programas embebidos y de juegos en C se han construido sin ningún uso de asignación de montón.
fuente
Aquí hay algunas respuestas excelentes sobre cómo asignar y liberar memoria, y en mi opinión, el lado más desafiante de usar C es asegurarse de que la única memoria que usa es la memoria que ha asignado; si esto no se hace correctamente, lo que finaliza está el primo de este sitio, un desbordamiento de búfer, y es posible que esté sobrescribiendo la memoria que está siendo utilizada por otra aplicación, con resultados muy impredecibles.
Un ejemplo:
En este punto, ha asignado 5 bytes para myString y lo ha llenado con "abcd \ 0" (las cadenas terminan en nulo - \ 0). Si su asignación de cadena fue
Estaría asignando "abcde" en los 5 bytes que ha asignado a su programa, y el carácter nulo final se colocaría al final de esto, una parte de la memoria que no ha sido asignada para su uso y podría ser gratis, pero también podría estar siendo utilizado por otra aplicación: esta es la parte crítica de la administración de la memoria, donde un error tendrá consecuencias impredecibles (y a veces irrepetibles).
fuente
strcpy()
lugar de=
; Supongo que esa fue la intención de Chris BC.Una cosa para recordar es siempre inicializar sus 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 de puntero sigan adelante silenciosamente. 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 "conectan" la dirección virtual 0x00000000 a excepciones de protección general para atrapar el uso del puntero nulo.
fuente
También es posible que desee utilizar la asignación de memoria dinámica cuando necesite definir una matriz enorme, digamos int [10000]. No puede simplemente ponerlo en la pila porque entonces, hm ... obtendrá un desbordamiento de la pila.
Otro buen ejemplo sería la implementación de una estructura de datos, digamos una lista enlazada o un árbol binario. No tengo un código de muestra para pegar aquí, pero puedes buscarlo en Google fácilmente.
fuente
(Estoy escribiendo porque siento que las respuestas hasta ahora no son del todo acertadas).
La razón por la que vale la pena mencionar la administración de la memoria es cuando tiene un problema / solución que requiere que cree estructuras complejas. (Si sus programas fallan si asigna mucho espacio en la pila a la vez, eso es un error). Por lo general, la primera estructura de datos que necesitará aprender es algún tipo de lista . Aquí hay uno solo enlazado, fuera de mi cabeza:
Naturalmente, le gustaría algunas otras funciones, pero básicamente, para esto es para lo que necesita la administración de memoria. Debo señalar que hay una serie de trucos que son posibles con la gestión de memoria "manual", por ejemplo,
Consiga un buen depurador ... ¡ Buena suerte!
fuente
@ Euro Micelli
Un aspecto negativo que se debe 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 de pila desde una función. Este es un error común y una de las principales razones por las que no puede funcionar solo con variables de pila. Si su función necesita devolver un puntero, entonces tiene que hacer malloc y ocuparse de la gestión de la memoria.
fuente
Estás en lo cierto, por supuesto. Creo que siempre ha sido así, aunque no tengo una copia de K&R para comprobar.
No me gustan muchas de las conversiones implícitas en C, así que tiendo a usar moldes para hacer que la "magia" sea más visible. A veces ayuda a la legibilidad, a veces no, ya veces hace que el compilador detecte un error silencioso. Aún así, no tengo una opinión firme sobre esto, de una forma u otra.
Sí ... me atrapaste allí. Paso mucho más tiempo en C ++ que en C. Gracias por darte cuenta.
fuente
En C, en realidad tienes dos opciones diferentes. Uno, puede dejar que el sistema administre la memoria por usted. Alternativamente, puede hacerlo usted mismo. En general, querrá ceñirse al primero el mayor tiempo posible. Sin embargo, la memoria administrada automáticamente en C es extremadamente limitada y necesitará administrar la memoria manualmente en muchos casos, como:
a. Desea que la variable sobreviva a las funciones y no quiere tener una variable global. ex:
si. desea tener memoria asignada dinámicamente. El ejemplo más común es una matriz sin longitud fija:
Mira, un valor largo es suficiente para contener CUALQUIER COSA. Solo recuerda liberarlo, o te arrepentirás. Este es uno de mis trucos favoritos para divertirme en C: D.
Sin embargo, en general, querrá mantenerse alejado de sus trucos favoritos (T___T). Romperás tu sistema operativo, tarde o temprano, si los usas con demasiada frecuencia. Siempre que no use * alloc y free, es seguro decir que todavía es virgen y que el código aún se ve bien.
fuente
Por supuesto. Si crea un objeto que existe fuera del alcance en el que lo usa. Aquí hay un ejemplo artificial (tenga en cuenta que mi sintaxis estará apagada; mi C está oxidada, pero este ejemplo aún ilustrará el concepto):
En este ejemplo, estoy usando un objeto de tipo SomeOtherClass durante la vida 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 útil del objeto y luego se libera una vez que se libera MyClass.
Obviamente, si este fuera un 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 tiene muchos objetos y desea controlarlos con precisión. cuando se crean y destruyen (para que su aplicación no consuma 1 GB de RAM durante toda su vida útil, por ejemplo), y en un entorno de ventana, esto es bastante obligatorio, ya que los objetos que crea (botones, por ejemplo) , debe existir fuera del alcance de cualquier función en particular (o incluso de clase).
fuente