¿Comprender el marco de pila de la llamada de función en C / C ++?

19

Estoy tratando de entender cómo se construyen los marcos de pila y qué variables (parámetros) se empujan para apilarse en qué orden. Algunos resultados de búsqueda mostraron que el compilador C / C ++ decide en función de las operaciones realizadas dentro de una función. Por ejemplo, si se suponía que la función incrementaba un valor int pasado por 1 (similar al operador ++) y lo devolvía, colocaría todos los parámetros de la función y las variables locales en registros.

Me pregunto qué registros se utilizan para los parámetros devueltos o pasados ​​por valor. ¿Cómo se devuelven las referencias? ¿Cómo elige el compilador entre eax, ebx, ecx y edx?

¿Qué necesito saber para entender cómo se usan, crean y destruyen los registros, las referencias de pila y montón durante las llamadas a funciones?

Gana
fuente
Esto es bastante difícil de leer (muro de texto). ¿Te importaría editar tu publicación en una mejor forma?
mosquito
1
Esta pregunta me parece bastante amplia. Además, ¿esto no va a ser muy específico de la plataforma?
Kazark
También se hizo una pregunta en SO: stackoverflow.com/questions/16088040/…
Wayne Conrad
Ver también mi respuesta en SO
Basile Starynkevitch

Respuestas:

11

Además de lo que dijo Dirk, un uso importante de los marcos de pila es guardar los valores anteriores de los registros para que puedan restaurarse después de una llamada a la función. Entonces, incluso en los procesadores donde los registros se usan para pasar parámetros, devolver un valor y guardar la dirección de retorno, los valores de esos registros se guardan en la pila antes de una llamada de función para que puedan restaurarse después de la llamada. Esto permite que una función llame a otra sin sobrescribir sus propios parámetros u olvidar su propia dirección de retorno.

Entonces, llamar a una función B desde la función A en un sistema "genérico" típico podría implicar los siguientes pasos:

  • función A:
    • presionar espacio para el valor de retorno
    • parámetros de empuje
    • empujar la dirección de retorno
  • saltar a la función B
  • función B:
    • empujar la dirección del marco anterior
    • valores de inserción de registros que utiliza esta función (para que puedan restaurarse)
    • empujar espacio para variables locales
    • hacer el cálculo necesario
    • restaurar los registros
    • restaurar el marco de pila anterior
    • almacenar el resultado de la función
    • saltar a la dirección del remitente
  • función A:
    • pop los parámetros
    • pop el valor de retorno

Esta no es la única forma en que las llamadas a funciones pueden funcionar (y puedo tener uno o dos pasos fuera de servicio), pero debería darle una idea de cómo se usa la pila para permitir que el procesador maneje las llamadas a funciones anidadas.

Caleb
fuente
¿Qué significa "empujar" exactamente aquí? No tengo idea de qué hacer con eso.
Tomáš Zato - Restablece a Mónica el
2
@ TomášZato pushy popson las dos operaciones fundamentales en una pila. Una pila es una estructura de último en entrar, primero en salir, como una pila de libros. Cuando tú push, estás poniendo un nuevo objeto encima de la pila; cuando popestás tomando un objeto desde la parte superior de la pila. No está permitido insertar o eliminar objetos en el medio, solo puede operar en la parte superior de la pila. Puede leer más sobre las pilas en general y la pila de programas en particular en Wikipedia.
Caleb
11

Esto depende de la convención de llamada utilizada. Quien defina la convención de convocatoria puede tomar esta decisión como quiera.

En la convención de llamadas más común en x86, los registros no se utilizan para pasar parámetros; los parámetros se envían a la pila comenzando con el parámetro más a la derecha. El valor de retorno se coloca en eax y puede usar edx si necesita espacio adicional. Las referencias y los punteros se devuelven en forma de una dirección en eax.

Dirk Holsopple
fuente
5

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 debes saber cómo funciona la función almacenar en la pila:

Almacenamiento dinámico de valores de asignación de memoria dinámica. Pila de almacenamiento automático de asignación y eliminación de valores.

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 la pila y qué son las partes de la pila:

ingrese la descripción de la imagen aquí

Asignación de la pila:

Recuerde una cosa si alguna función obtiene "retorno" sin importar que haya cargado todas sus variables locales o cualquier cosa que regrese inmediatamente de la pila a su marco de pila. Significa que cuando cualquier función recursiva obtiene la condición base y colocamos el retorno después de la condición base para que la condición base no espere a cargar las variables locales que están situadas en la parte "else" del programa, inmediatamente devolverá el cuadro actual de la pila y ahora si un cuadro volver el siguiente cuadro está 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 la declaración return, elimina el marco actual de la pila.

mientras regresa del valor de la pila, regresará en orden inverso al orden en que se asignaron en la pila.

ingrese la descripción de la imagen aquí

Estas son descripciones muy cortas y si quieres saber más sobre la pila y la doble recursión, lee dos publicaciones de este blog:

Más acerca de la pila paso a paso

Más acerca de la doble recursión paso a paso con stack

usuario5904928
fuente
3

Lo que está buscando se llama Application Binary Interface - ABI.

Hay una especificación para cada compilador que detalla el ABI.

Cada plataforma generalmente especificará y ABI para soportar la interoperabilidad entre compiladores. Por ejemplo, las convenciones de llamadas x86 explican las convenciones de llamadas típicas para x86 y x86-64. Sin embargo, esperaría un documento más oficial que wikipedia.

Bill Door
fuente