Saltar al contenido

¿Cómo funciona realmente la asignación automática de memoria en C ++?

Ya no necesitas buscar más en internet porque has llegado al sitio perfecto, contamos con la solución que necesitas hallar sin complicaciones.

Solución:

Para comprender mejor lo que está sucediendo, imaginemos que solo tenemos un sistema operativo muy primitivo que se ejecuta en un procesador de 16 bits que puede ejecutar solo un proceso a la vez. Es decir: solo se puede ejecutar un programa a la vez. Además, supongamos que todas las interrupciones están desactivadas.

Hay una construcción en nuestro procesador llamada pila. La pila es una construcción lógica impuesta a la memoria física. Digamos que nuestra RAM existe en las direcciones E000 a FFFF. Esto significa que nuestro programa en ejecución puede utilizar esta memoria de la forma que queramos. Imaginemos que nuestro sistema operativo dice que E000 a EFFF es la pila y F000 a FFFF es el montón.

La pila se mantiene mediante el hardware y las instrucciones de la máquina. Realmente no hay mucho que necesitemos hacer para mantenerlo. Todo lo que nosotros (o nuestro sistema operativo) debemos hacer es asegurarnos de establecer una dirección adecuada para el inicio de la pila. El puntero de pila es una entidad física que reside en el hardware (procesador) y es administrado por las instrucciones del procesador. En este caso, nuestro puntero de pila se establecería en EFFF (asumiendo que la pila crece HACIA ATRÁS, lo cual es bastante común, -). Con un lenguaje compilado como C, cuando llamas a una función, empuja cualquier argumento que hayas pasado a la función en la pila. Cada argumento tiene un cierto tamaño. int es usualmente de 16 o 32 bits, char es usualmente de 8 bits, etc. Supongamos que en nuestro sistema, int e int * son de 16 bits. Para cada argumento, el puntero de la pila se DISMINUYE (-) por tamaño de (argumento), y el argumento se copia en la pila. Luego, cualquier variable que haya declarado en el alcance se inserta en la pila de la misma manera, pero sus valores no se inicializan.

Reconsideremos dos ejemplos similares a sus dos ejemplos.

int hello(int eeep)

    int i;
    int *p;

Lo que sucede aquí en nuestro sistema de 16 bits es lo siguiente: 1) insertar eeep en la pila. Esto significa que disminuimos el puntero de la pila a EFFD (porque sizeof (int) es 2) y luego copiamos eeep a la dirección EFFE (el valor actual de nuestro puntero de la pila, menos 1 porque nuestro puntero de la pila apunta al primer lugar que está disponible después de la asignación). A veces hay instrucciones que pueden hacer ambas cosas de una sola vez (asumiendo que está copiando datos que quepan en un registro. De lo contrario, tendría que copiar manualmente cada elemento de un tipo de datos en su lugar apropiado en la pila – ¡el orden importa! ).

2) crea espacio para i. Esto probablemente significa simplemente disminuir el puntero de la pila a EFFB.

3) crea espacio para p. Esto probablemente significa simplemente disminuir el puntero de la pila a EFF9.

Luego, nuestro programa se ejecuta, recordando dónde viven nuestras variables (eeep comienza en EFFE, i en EFFC yp en EFFA). Lo importante a recordar es que aunque la pila cuenta HACIA ATRÁS, las variables aún operan HACIA ADELANTE (esto en realidad depende de la endianidad, pero el punto es que & eeep == EFFE, no EFFF).

Cuando la función se cierra, simplemente incrementamos (++) el puntero de la pila en 6, (porque se han insertado 3 “objetos”, no del tipo c ++, de tamaño 2 en la pila.

Ahora, su segundo escenario es mucho más difícil de explicar porque hay tantos métodos para lograrlo que es casi imposible de explicar en Internet.

int hello(int eeep)

    int *p = malloc(sizeof(int));//C's pseudo-equivalent of new
    free(p);//C's pseudo-equivalent of delete

eeep yp todavía se insertan y asignan en la pila como en el ejemplo anterior. En este caso, sin embargo, inicializamos p con el resultado de una llamada a la función. Lo que malloc (o new, pero new hace más en c ++. Llama a los constructores cuando es apropiado, y todo lo demás) es que va a esta caja negra llamada HEAP y obtiene una dirección de memoria libre. Nuestro sistema operativo administrará el montón por nosotros, pero tenemos que avisarle cuando queremos memoria y cuando terminamos con ella.

En el ejemplo, cuando llamamos a malloc (), el sistema operativo devolverá un bloque de 2 bytes (sizeof (int) en nuestro sistema es 2) dándonos la dirección inicial de estos bytes. Digamos que la primera llamada nos dio la dirección F000. Luego, el sistema operativo realiza un seguimiento de las direcciones F000 y F001 que están actualmente en uso. Cuando llamamos a free (p), el sistema operativo encuentra el bloque de memoria al que apunta p y marca 2 bytes como no utilizados (porque sizeof (estrella p) es 2). Si, en cambio, asignamos más memoria, la dirección F002 probablemente se devolverá como el bloque inicial de la nueva memoria. Tenga en cuenta que malloc () en sí mismo es una función. Cuando p se inserta en la pila para la llamada de malloc (), la p se copia nuevamente en la pila en la primera dirección abierta que tenga suficiente espacio en la pila para ajustarse al tamaño de p (probablemente EFFB, porque solo presionamos 2 cosas en la pila esta vez de tamaño 2, y sizeof (p) es 2), y el puntero de la pila se reduce nuevamente a EFF9, y malloc () pondrá sus variables locales en la pila comenzando en esta ubicación. Cuando malloc termina, saca todos sus elementos de la pila y establece el puntero de la pila en lo que era antes de ser llamado. El valor de retorno de malloc (), un void estrella, probablemente se colocará en algún registro (generalmente el acumulador en muchos sistemas) para nuestro uso.

En la implementación, ambos ejemplos REALMENTE no son tan simples. Cuando asigna memoria de pila, para una nueva llamada de función, debe asegurarse de guardar su estado (guardar todos los registros) para que la nueva función no borre los valores de forma permanente. Por lo general, esto también implica empujarlos hacia la pila. De la misma manera, generalmente guardará el registro del contador del programa para que pueda regresar al lugar correcto después de que regrese la subrutina. Los administradores de memoria usan su propia memoria para “recordar” qué memoria se ha entregado y cuál no. La memoria virtual y la segmentación de la memoria complican aún más este proceso, y los algoritmos de gestión de la memoria deben mover continuamente los bloques (y protegerlos también) para evitar la fragmentación de la memoria (todo un tema en sí mismo), y esto se relaciona con la memoria virtual. así como. El segundo ejemplo realmente es una gran lata de gusanos comparado con el primer ejemplo. Además, ejecutar varios procesos hace que todo esto sea mucho más complicado, ya que cada proceso tiene su propia pila y más de un proceso puede acceder al montón (lo que significa que debe protegerse a sí mismo). Además, cada arquitectura de procesador es diferente. Algunas arquitecturas esperarán que establezca el puntero de la pila en la primera dirección libre en la pila, otras esperarán que lo apunte al primer lugar no libre.

Espero que esto haya ayudado. Por favor hagamelo saber.

Tenga en cuenta que todos los ejemplos anteriores son para una máquina ficticia que está demasiado simplificada. En hardware real, esto se vuelve un poco más complicado.

editar: los asteriscos no aparecen. los reemplacé con la palabra “estrella”


Por lo que vale, si usamos (principalmente) el mismo código en los ejemplos, reemplazando “hola” con “ejemplo1” y “ejemplo2”, respectivamente, obtenemos la siguiente salida ensamblada para intel en wndows.

    .file   "test1.c"
    .text
.globl _example1
    .def    _example1;  .scl    2;  .type   32; .endef
_example1:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    leave
    ret
.globl _example2
    .def    _example2;  .scl    2;  .type   32; .endef
_example2:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    movl    $4, (%esp)
    call    _malloc
    movl    %eax, -4(%ebp)
    movl    -4(%ebp), %eax
    movl    %eax, (%esp)
    call    _free
    leave
    ret
    .def    _free;  .scl    3;  .type   32; .endef
    .def    _malloc;    .scl    3;  .type   32; .endef

No, sin optimización …

int main() 
      
    int i; 
    int *p; 

no hace casi nada, solo un par de instrucciones para ajustar el puntero de la pila, pero

int main() 
 
    int *p = new int; 
    delete p; 

asigna un bloque de memoria en el montón y luego lo libera, eso es mucho trabajo (lo digo en serio, la asignación del montón no es una operación trivial).

    int i;
    int *p;

^ Asignación de un entero y un puntero de entero en la pila

int *p = new int;
delete p;

^ Asignación de un puntero entero en la pila y bloque del tamaño del entero en el montón

EDITAR:

Diferencia entre segmento de pila y segmento de montón

texto alternativo


(fuente: maxi-pedia.com)

void another_function()
   int var1_in_other_function;   /* Stack- main-y-sr-another_function-var1_in_other_function */
   int var2_in_other_function;/* Stack- main-y-sr-another_function-var1_in_other_function-var2_in_other_function */

int main()                      /* Stack- main */
   int y;                        /* Stack- main-y */
   char str;                     /* Stack- main-y-sr */
   another_function();           /*Stack- main-y-sr-another_function*/
   return 1 ;                    /* Stack- main-y-sr */ //stack will be empty after this statement                        

Siempre que un programa comienza a ejecutarse, almacena todas sus variables en una ubicación especial de memoria llamada Segmento de pila. Por ejemplo, en el caso de C / C ++, la primera función llamada es main. por lo que se colocará primero en la pila. Cualquier variable dentro de main se colocará en la pila a medida que se ejecute el programa. Ahora, como principal es la primera función llamada, será la última función en devolver cualquier valor (O se extraerá de la pila).

Ahora, cuando asigna memoria dinámicamente usando new se utiliza otra ubicación de memoria especial llamada segmento de montón. Incluso si los datos reales están presentes en el puntero del montón, se encuentran en la pila.

Comentarios y calificaciones del post

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *