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

15

Estoy tratando de entender cómo se crean los marcos de pila y qué variables (parámetros) se empujan para apilar en qué orden? Algunos resultados de búsqueda mostraron que el compilador de 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 solo incrementaría un valor int pasado en 1 (similar al operador ++) y lo devolvería, pondrí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 comprender cómo se utilizan, construyen y destruyen los registros, las pilas y las referencias de almacenamiento dinámico durante las llamadas a funciones?

    
pregunta Gana 18.04.2013 - 18:58
fuente

4 respuestas

8

Además de lo que dijo Dirk, un uso importante de los marcos de pila es guardar los valores previos de los registros para que puedan restaurarse después de una llamada de función. Por lo tanto, incluso en los procesadores donde los registros se utilizan 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.

Por lo tanto, llamar a una función B desde la función A en un sistema "genérico" típico puede incluir los siguientes pasos:

  • función A:
    • espacio de inserción para el valor de retorno
    • parámetros de inserción
    • presione la dirección de retorno
  • salta a la función B
  • función B:
    • empuje la dirección del marco de pila anterior
    • valores de inserción de los registros que utiliza esta función (para que puedan restaurarse)
    • espacio de inserción 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 de retorno
  • función A:
    • abre los parámetros
    • reventar el valor de retorno

Esta no es de ninguna manera la única forma en que pueden funcionar las llamadas de función (y es posible que tenga uno o dos pasos fuera de orden), pero debería darle una idea de cómo se utiliza la pila para permitir que el procesador maneje las llamadas de función anidadas .

    
respondido por el Caleb 18.04.2013 - 23:50
fuente
9

Esto depende de la convención de llamada que se utilice. Quien defina la convención de llamada puede tomar esta decisión como quieran.

En la convención de llamadas más común en x86, los registros no se usan para pasar parámetros; los parámetros se insertan en 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 el espacio adicional. Las referencias y los punteros se devuelven en forma de una dirección en eax.

    
respondido por el Dirk Holsopple 18.04.2013 - 19:25
fuente
4

Si entiendes muy bien la pila, entenderás cómo funciona la memoria en el programa y si comprendes cómo funciona la memoria en el programa entenderás cómo se almacena la función en el programa y si comprendes cómo se almacena la función en el programa entenderás cómo funciona la función recursiva Funciona y si comprende cómo funciona la función recursiva, entenderá cómo funciona el compilador y si entiende cómo funciona el compilador, su mente funcionará como compilador y depurará cualquier programa muy fácilmente

Déjame explicarte cómo funciona la pila:

Primero tienes que saber cómo se almacena la función en la pila:

Heap almacena los valores de asignación de memoria dinámica. La pila almacena valores automáticos de asignación y eliminación.

Entendamosconelejemplo:

defhello(x):ifx==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 entiendo partes de este programa:

Ahoraveamosquéeslapilayquésonlaspartesdelapila:

  

Asignacióndelapila:

Recuerdeunacosasicualquierfunciónobtiene"retorno", sin importar que haya cargado todas sus variables locales o cualquier cosa que retorne inmediatamente de la pila, su marco de pila. Significa que cuando una función recursiva obtiene una condición de base y ponemos retorno después de la condición de base para que la condición de base no espere a cargar las variables locales que se encuentran en la parte "else" del programa, devolverá inmediatamente el cuadro actual de la pila y ahora si un cuadro volver el siguiente cuadro está en el registro de activación. Ver esto en la práctica:

  

Desasignacióndelbloque:

Entonces,cadavezqueunafunciónencuentreunadeclaraciónderetorno,borraelfotogramaactualdelapila.

mientrasregresadelvalordelapila,regresaráenelordeninversoalordenenqueseasignaronenlapila.

Estassondescripcionesmuycortasysiquieressabermásacercadelapilayladoblerecursión,leelasdospublicacionesdeesteblog:

Más información sobre la pila paso a paso

Más información sobre la doble recursión paso a paso con la pila

    
respondido por el user5904928 18.10.2016 - 13:05
fuente
3

Lo que está buscando se llama Interfaz binario de la aplicación - ABI.

Hay una especificación para cada compilador que explica la ABI.

Por lo general, cada plataforma especificará y ABI con el fin de admitir la interoperabilidad entre compiladores. Por ejemplo, Convenciones de llamada x86 explica las convenciones de llamada típicas para x86 y x86-64. Sin embargo, esperaría un documento más oficial que wikipedia.

    
respondido por el Bill Door 19.04.2013 - 03:56
fuente

Lea otras preguntas en las etiquetas