Introduccion
Hoy, debido a la mejora y el abaratamiento de la tecnología, la cantidad de memoria y poder de procesamiento está creciendo constantemente.
De acuerdo con la Ley de Moore:
El número de transistores colocados en un chip de circuito integrado se duplica cada 24 meses.
Cabe señalar que se cambian dos parámetros:
- Número de transistores
- Dimensiones del módulo
Se proyectan los mismos principios sobre la cantidad de RAM (DRAM).
Ahora el problema de la memoria no es grave, ya que la cantidad de memoria en los últimos 10 años ha aumentado 16 veces por dado.
La mayoría de los lenguajes de programación de alto nivel (PL) ya están "listos para usar" y ocultan el trabajo con la memoria del programador. Y, dado que esta pregunta estaba dormida, aparece una nueva casta de programadores que no entienden
o no quieren entender cómo funciona el trabajo con la memoria.
En este tema, consideraremos los puntos principales de trabajar con memoria usando el ejemplo del lenguaje C ++, porque es uno de los pocos lenguajes imperativos que admite el trabajo directo con memoria y admite OOP.
¿Para qué sirve?
Vale la pena mencionar aquí, este artículo está diseñado para personas que recién están comenzando su camino en C ++ o simplemente quieren tener una idea sobre la memoria dinámica.
En tiempo de ejecución, cualquier programa reserva una pieza de memoria para sí mismo en DRAM. El resto del espacio libre de DRAM se denomina
"montón" ("montón" en inglés). La asignación de memoria durante la ejecución para las necesidades del programa ocurre precisamente desde el montón y se llama asignación de memoria dinámica.
El problema es que si no se ocupa de limpiar la memoria asignada cuando ya no se necesita, puede producirse una llamada pérdida de memoria, en la que su sistema
(programa) simplemente se bloquea. Similar a un automóvil que se detuvo en medio del camino porque alguien olvidó repostarlo a tiempo.
Lo que ya deberías saberLa mayoría de los PL modernos están equipados con recolectores de basura y limpian su memoria por sí mismos.
Sin embargo, C ++ se ha establecido como una de las API de rendimiento más rápido, en parte porque todo el trabajo con memoria se realiza manualmente.
nuevo y eliminar
La asignación de memoria puede ser estática y dinámica. La asignación estática de memoria se denomina asignación única de memoria durante la compilación del programa, y la cantidad de memoria estática no cambia en el tiempo de ejecución. Un ejemplo clásico es la declaración de una variable o matriz entera. Pero, ¿qué pasa si el programador no sabe de antemano cuántos elementos se necesitan en el contenedor?
El uso de memoria dinámica es aconsejable cuando es necesario organizar la asignación de memoria para las necesidades del programa según sea necesario.
El
nuevo operador es responsable de asignar memoria dinámica en C ++, y
delete es responsable de
borrarlo .
El
nuevo operador devuelve el resultado de su operación un puntero a una nueva instancia de la clase.
La sintaxis es esta:
El |
puntero de tipo de datos (T1) | * |
nombre del puntero | =
nuevo |
tipo T1 |;
Después del
nuevo operador, puede usar el constructor, por ejemplo, para inicializar los campos de la clase.
Vale la pena señalar que la misma pérdida de memoria ocurre exactamente cuando el programador pierde el control sobre su asignación.
Es importante recordar:
Si se ha olvidado de borrar la memoria dinámica de los elementos innecesarios "gastados", tarde o temprano llegará un momento crítico en el que simplemente no se puede extraer la memoria.
Un ejemplo de asignación de memoria y su limpieza:
int main{
Este artículo no discutirá los llamados punteros "inteligentes", ya que el tema es muy extenso, pero, en resumen: "Los punteros inteligentes automatizan parcialmente el proceso de limpieza de memoria para el programador".
Punteros
Los punteros son responsables de trabajar con memoria dinámica en C ++. Este es un tema del cual el apetito echa a perder a los principiantes.
Puede declarar un puntero utilizando el operador
* . Por defecto, apuntará a alguna región aleatoria de la memoria. Para poder acceder al área de memoria que necesitamos, necesitamos pasar un enlace (operador
& ) a la variable deseada.
El puntero en sí mismo es simplemente la dirección de una celda de memoria, y para acceder a los datos almacenados en esta celda, debe desreferenciarse.
Retiro importante
Si intenta mostrar el puntero sin desreferenciar, en lugar del valor del área de memoria al que apunta, se muestra la dirección de esta área de memoria.
Para desreferenciar un puntero, simplemente coloque el operador * delante de su nombre.
int main() {

Mirando estos ejemplos, me gustaría preguntar: "¿Por qué es esto necesario si puedes derivar una variable de inmediato?"
Otro ejemplo:
Tenemos una clase de programadores que describe a los miembros de un equipo de programadores que
no conocen los punteros. class Programmers{ public: Programmers(){} Programmers(int iWeight, int iAge){ this->weight = iWeight; this->age = iAge; } int weight; int age; }; int main() {
De esta manera, la memoria puede ser manipulada como queramos. Y es por eso que, cuando trabajas con memoria, puedes "dispararte en el pie". Cabe señalar que trabajar con el puntero es mucho más rápido, ya que el valor en sí no se copia, sino que solo se le asigna un enlace a una dirección específica.
Por cierto, una palabra clave tan popular que proporciona un puntero al objeto de clase actual.
Estos punteros están en todas partes.Un ejemplo de un puntero en la vida cotidiana:Imagine una situación cuando ordena un plato en un restaurante. Para hacer un pedido, solo tiene que señalar el plato en el menú y estará preparado. Del mismo modo, otros visitantes del restaurante indican el elemento deseado en el menú. Por lo tanto, cada línea en el menú es un puntero a la función de cocción de un plato, y este puntero se creó en la etapa de diseño de este menú.
Ejemplo de puntero de función De vuelta a nuestros programadores. Supongamos que ahora necesitamos llevar los campos de clase a la sección
privada , como corresponde al principio de encapsulación desde OOP, entonces necesitamos obtener
getter para obtener acceso de lectura a estos campos. Pero imagina que no tenemos 2 campos, sino 100, y para esto necesitamos escribir nuestro propio descriptor de acceso para cada uno.
SpoilerBueno, por supuesto que no, ni siquiera entiendo por qué abriste este spoiler.
Para hacer esto, crearemos un "descriptor de acceso" de tipo void y le pasaremos argumentos por referencia. El significado de pasar un argumento por referencia es que el valor del argumento no se copia, sino que solo se transmite la dirección del argumento real. Por lo tanto, al cambiar el valor de dicho argumento, los datos en la celda de memoria del argumento actual también cambiarán.
Esto también afecta el rendimiento general, ya que pasar un argumento por referencia es más rápido que pasar por valor. Y esto sin mencionar las grandes colecciones de elementos.
Por ejemplo, el método
getParams dentro cambiará los argumentos entrantes y cambiarán sus valores, incluso en el ámbito, desde donde se llamó.
Un puntero nos ayudará a navegar por la matriz. Por la teoría de las estructuras de datos, sabemos que una matriz es una región continua de memoria cuyos elementos están dispuestos uno tras otro.
Esto significa que si cambia el valor del puntero al número de bytes que ocupa el elemento en la matriz, puede alcanzar cada elemento hasta que el puntero vaya más allá de los límites de la matriz.
Cree otro puntero que apunte al primer elemento de la matriz de
programadores .
class Programmers{ public: Programmers(){} Programmers(int iWeight, int iAge){ this->weight = iWeight; this->age = iAge; }

En este ejemplo, quiero transmitirle la esencia del hecho de que cuando cambia el valor de la dirección del puntero, puede acceder a otra área de la memoria.
Estructuras de datos como listas, vectores, etc. basado en punteros, y por lo tanto llamado estructuras de datos dinámicos. Y para iterar sobre ellos es más correcto usar iteradores. Un iterador es un puntero a un elemento de la estructura de datos y proporciona acceso al elemento del contenedor.
En conclusión
Habiendo entendido el tema de los punteros, trabajar con la memoria se convierte en una parte agradable de la programación, y en su conjunto aparece una comprensión detallada de cómo funciona la máquina con la memoria y cómo administrarla. En cierto sentido, hay una filosofía detrás del concepto mismo de "Trabajar con memoria". Al alcance de su mano, puede cambiar la carga en las placas de condensadores incluso muy pequeños.