Explicar el concepto de un marco de pila en pocas palabras

200

Parece que tengo la idea de la pila de llamadas en el diseño del lenguaje de programación. Pero no puedo encontrar (probablemente, simplemente no busco lo suficiente) ninguna explicación decente de qué es el marco de la pila .

Así que me gustaría pedirle a alguien que me lo explique en pocas palabras.

ikostia
fuente

Respuestas:

195

Un marco de pila es un marco de datos que se inserta en la pila. En el caso de una pila de llamadas, un marco de pila representaría una llamada de función y sus datos de argumento.

Si no recuerdo mal, la dirección de retorno de la función se inserta primero en la pila, luego los argumentos y el espacio para las variables locales. Juntos, hacen el "marco", aunque es probable que esto dependa de la arquitectura. El procesador sabe cuántos bytes hay en cada cuadro y mueve el puntero de la pila en consecuencia a medida que los cuadros se empujan y salen de la pila.

EDITAR:

Hay una gran diferencia entre las pilas de llamadas de nivel superior y la pila de llamadas del procesador.

Cuando hablamos de la pila de llamadas de un procesador, estamos hablando de trabajar con direcciones y valores a nivel de byte / palabra en ensamblado o código de máquina. Hay "pilas de llamadas" cuando se habla de lenguajes de nivel superior, pero son una herramienta de depuración / tiempo de ejecución administrada por el entorno de tiempo de ejecución para que pueda registrar lo que salió mal con su programa (en un nivel alto). En este nivel, a menudo se conocen cosas como números de línea y métodos y nombres de clase. Cuando el procesador obtiene el código, no tiene absolutamente ningún concepto de estas cosas.

Tony R
fuente
66
"El procesador sabe cuántos bytes hay en cada cuadro y mueve el puntero de la pila en consecuencia a medida que los cuadros se empujan y salen de la pila". - Dudo que el procesador sepa algo sobre el stack, porque NOSOTROS lo manipulamos mediante subbing (asignación), empujando y haciendo estallar. Y aquí están las convenciones de llamadas que explican cómo debemos usar la pila.
Victor Polevoy
78

Si comprende muy bien la pila, comprenderá cómo funciona la memoria en el programa y si comprende cómo funciona la memoria en el programa, comprenderá cómo funciona la función almacenar en el programa y si comprende cómo funciona la función almacenar en el programa, comprenderá cómo funciona la función recursiva y si comprende cómo funciona la función recursiva, comprenderá cómo funciona el compilador y si comprende cómo funciona, su mente funcionará como compilador y depurará cualquier programa muy fácilmente

Déjame explicarte cómo funciona la pila:

Primero debe saber cómo se representan las funciones en la pila:

El almacenamiento dinámico almacena valores asignados dinámicamente.
La pila almacena valores de asignación y eliminación automáticos.

ingrese la descripción de la imagen aquí

Vamos a entender con el ejemplo:

def hello(x):
    if x==1:
        return "op"
    else:
        u=1
        e=12
        s=hello(x-1)
        e+=1
        print(s)
        print(x)
        u+=1
    return e

hello(4)

Ahora entienda partes de este programa:

ingrese la descripción de la imagen aquí

Ahora veamos qué es stack y cuáles son las partes de stack:

ingrese la descripción de la imagen aquí

Asignación de la pila:

Recuerde una cosa: si se cumple la condición de retorno de cualquier función, no importa si ha cargado las variables locales o no, regresará inmediatamente de la pila con su marco de pila. Significa que cada vez que una función recursiva obtiene la condición base satisfecha y ponemos un retorno después de la condición base, la condición base no esperará para cargar las variables locales que se encuentran en la parte "else" del programa. Inmediatamente devolverá el cuadro actual de la pila después de lo cual el siguiente cuadro está ahora en el registro de activación.

Vea esto en la práctica:

ingrese la descripción de la imagen aquí

Desasignación del bloque:

Entonces, cada vez que una función encuentra una declaración return, elimina el marco actual de la pila.

Al regresar de la pila, los valores se devolverán al revés del orden original en el que se asignaron en la pila.

ingrese la descripción de la imagen aquí

Aaditya Ura
fuente
3
la pila crece hacia abajo y el montón crece hacia arriba, los tienes invertidos en tu diagrama. DIAGRAMA CORRECTO AQUÍ
Rafael
@ Rafael, perdón por la confusión, estaba hablando de la dirección del crecimiento, no estaba hablando de la dirección del crecimiento de la pila. Hay una diferencia entre la dirección del crecimiento y la dirección del crecimiento de la pila. Vea aquí stackoverflow.com/questions/1677415/…
Aaditya Ura el
2
Rafael tiene razón. También la primera imagen está mal. Reemplácelo con algo más (busque "pila de montón" en google pictures).
Nikos
Entonces, si entiendo correctamente, en su tercer diagrama, hay 3 marcos de pila porque se hello()ha llamado recursivamente a lo hello()que luego se ha llamado (nuevamente) recursivamente hello(), y el marco global es la función original que se llamó primero hello().
Andy J
1
¿A dónde nos llevan los enlaces? Como una preocupación seria de seguridad, estos enlaces deben eliminarse lo antes posible.
Shivanshu
45

Un resumen rápido. Quizás alguien tenga una mejor explicación.

Una pila de llamadas se compone de 1 o varias tramas de pila. Cada marco de pila corresponde a una llamada a una función o procedimiento que aún no ha terminado con un retorno.

Para usar un marco de pila, un hilo mantiene dos punteros, uno se llama Puntero de pila (SP) y el otro se llama Puntero de marco (FP). SP siempre apunta a la "parte superior" de la pila, y FP siempre apunta a la "parte superior" del marco. Además, el hilo también mantiene un contador de programa (PC) que apunta a la siguiente instrucción que se ejecutará.

Lo siguiente se almacena en la pila: variables locales y temporales, parámetros reales de la instrucción actual (procedimiento, función, etc.)

Existen diferentes convenciones de llamadas con respecto a la limpieza de la pila.

ervinbosenbacher
fuente
77
No olvide que la dirección de retorno de la subrutina va en la pila.
Tony R
44
Frame Pointer también es Base Pointer en términos x86
peterchaula
1
Me gustaría enfatizar que un puntero de cuadro apunta al comienzo del cuadro de pila para la encarnación del procedimiento actualmente activo.
Servidor Khalilov
13

"Una pila de llamadas se compone de marcos de pila ..." -  Wikipedia

Un marco de pila es algo que pones en la pila. Son estructuras de datos que contienen información sobre subrutinas para llamar.

Waleed Khan
fuente
Lo siento, no tengo idea de cómo me perdí esto en wiki. Gracias. ¿Entiendo correctamente que en lenguajes dinámicos el tamaño de la trama no es un valor constante ya que los locales de la función no se conocen exactamente?
ikostia
El tamaño y la naturaleza de un marco dependen en gran medida de la arquitectura de la máquina. De hecho, el paradigma mismo de una pila de llamadas es específico de la arquitectura. Hasta donde sé, siempre es variable porque diferentes llamadas a funciones tendrán diferentes cantidades de datos de argumentos.
Tony R
Tenga en cuenta que el tamaño del marco de la pila debe ser conocido por el procesador cuando se manipula. Cuando esto sucede, el tamaño de los datos ya está determinado. Los lenguajes dinámicos se compilan en código máquina al igual que los lenguajes estáticos, pero a menudo se realizan justo a tiempo para que el compilador pueda mantener el dinamismo y el procesador pueda trabajar con tamaños de trama "conocidos". No confunda los lenguajes de nivel superior con el código / ensamblaje de la máquina, que es donde realmente sucede esto.
Tony R
Bueno, pero los lenguajes dinámicos también tienen sus pilas de llamadas, ¿no? Quiero decir, si, por ejemplo, Python quiere ejecutar algún procedimiento, los datos sobre este procedimiento se almacenan dentro de la estructura de algún intérprete de Python, ¿estoy en lo cierto? Entonces, quiero decir que la pila de llamadas está presente no solo en un nivel bajo.
ikostia
Después de leer un poco de ese artículo de Wikipedia, estoy corregido (un poco). El tamaño del marco de la pila puede permanecer desconocido en el momento de la compilación . Pero para cuando el procesador esté trabajando con punteros stack + frame, tiene que saber cuáles son los tamaños. El tamaño puede ser variable pero el procesador sabe el tamaño, es lo que estaba tratando de decir.
Tony R
5

Los programadores pueden tener preguntas sobre los marcos de la pila no en un término amplio (que es una entidad simple en la pila que sirve solo una llamada de función y mantiene la dirección de retorno, los argumentos y las variables locales), pero en un sentido estricto, cuando el término stack framesse menciona en contexto de las opciones del compilador.

Si el autor de la pregunta lo ha querido decir o no, pero el concepto de un marco de pila desde el aspecto de las opciones del compilador es un tema muy importante, no cubierto por las otras respuestas aquí.

Por ejemplo, el compilador C / C ++ de Microsoft Visual Studio 2015 tiene la siguiente opción relacionada con stack frames:

  • / Oy (omisión de puntero de marco)

GCC tiene lo siguiente:

  • -fomit-frame-pointer (No mantenga el puntero de cuadro en un registro para funciones que no lo necesitan. Esto evita las instrucciones para guardar, configurar y restaurar punteros de cuadro; también hace que un registro adicional esté disponible en muchas funciones )

El compilador Intel C ++ tiene lo siguiente:

  • -fomit-frame-pointer (Determina si EBP se usa como un registro de propósito general en optimizaciones)

que tiene el siguiente alias:

  • / Oy

Delphi tiene la siguiente opción de línea de comandos:

  • - $ W + (Generar marcos de pila)

En ese sentido específico, desde la perspectiva del compilador, un marco de pila es solo el código de entrada y salida para la rutina , que empuja un ancla a la pila, que también se puede usar para la depuración y para el manejo de excepciones. Las herramientas de depuración pueden escanear los datos de la pila y usar estos anclajes para retroceder, mientras se ubican call sitesen la pila, es decir, para mostrar los nombres de las funciones en el orden en que se han llamado jerárquicamente. Para la arquitectura Intel, es push ebp; mov ebp, espo enterpara entrada y / mov esp, ebp; pop ebpo leavesalida.

Es por eso que es muy importante comprender para un programador en qué consiste un marco de pila cuando se trata de opciones del compilador, porque el compilador puede controlar si generar este código o no.

En algunos casos, el compilador puede omitir el marco de la pila (código de entrada y salida para la rutina), y se puede acceder directamente a las variables a través del puntero de la pila (SP / ESP / RSP) en lugar del conveniente puntero base (BP / ESP / RSP). Condiciones para la omisión del marco de la pila, por ejemplo:

  • la función es una función hoja (es decir, una entidad final que no llama a otras funciones);
  • no hay intentos / finalmente o intentos / excepto o construcciones similares, es decir, no se utilizan excepciones;
  • no se llaman rutinas con parámetros salientes en la pila;
  • la función no tiene parámetros;
  • la función no tiene código de ensamblaje en línea;
  • etc ...

Omitir marcos de pila (código de entrada y salida para la rutina) puede hacer que el código sea más pequeño y más rápido, pero también puede afectar negativamente la capacidad de los depuradores para rastrear los datos en la pila y mostrarlos al programador. Estas son las opciones del compilador que determinan en qué condiciones una función debe tener el código de entrada y salida, por ejemplo: (a) siempre, (b) nunca, (c) cuando sea necesario (especificando las condiciones).

Maxim Masiutin
fuente
-1

El marco de pila es la información empaquetada relacionada con una llamada de función. Esta información generalmente incluye argumentos pasados ​​a la función th, variables locales y dónde regresar al finalizar. El registro de activación es otro nombre para un marco de pila. El fabricante determina el diseño del marco de la pila en el ABI y cada compilador que admita el ISA debe cumplir con este estándar, sin embargo, el esquema de diseño puede depender del compilador. En general, el tamaño del marco de la pila no está limitado, pero existe un concepto llamado "zona roja / protegida" para permitir que las llamadas al sistema ... etc. se ejecuten sin interferir con un marco de la pila.

Siempre hay un SP pero en algunos ABI (ARM y PowerPC por ejemplo) FP es opcional. Los argumentos que debían colocarse en la pila se pueden compensar utilizando solo el SP. Si se genera un marco de pila para una llamada a función o no, depende del tipo y la cantidad de argumentos, las variables locales y cómo se accede a las variables locales en general. En la mayoría de los ISA, primero, se usan registros y si hay más argumentos que registros dedicados a pasar argumentos, estos se colocan en la pila (por ejemplo, x86 ABI tiene 6 registros para pasar argumentos enteros). Por lo tanto, a veces, algunas funciones no necesitan un marco de pila para colocarse en la pila, solo la dirección de retorno se inserta en la pila.

tetera borracha
fuente