resueltos pilas pila listas estructura estatica ejercicios dinamicas datos comparar colas cola codigo c++ c assembly

c++ - pilas - ¿Qué es exactamente el puntero base y el puntero de pila? ¿A qué apuntan?



pilas en c++ ejercicios resueltos (8)

ESP es el puntero de la pila actual, que cambiará cada vez que una palabra o dirección se empuje o salte de la pila. EBP es una forma más conveniente para que el compilador realice un seguimiento de los parámetros y variables locales de una función que utilizando el ESP directamente.

Generalmente (y esto puede variar de compilador a compilador), todos los argumentos a una función que se llama se insertan en la pila (generalmente en el orden inverso al que se declaran en el prototipo de función, pero esto varía). Luego se llama a la función, que empuja la dirección de retorno (EIP) a la pila.

Al ingresar a la función, el antiguo valor de EBP se inserta en la pila y EBP se establece en el valor de ESP. Luego, el ESP disminuye (porque la pila crece hacia abajo en la memoria) para asignar espacio para las variables locales y temporales de la función. A partir de ese momento, durante la ejecución de la función, los argumentos de la función se ubican en la pila en las compensaciones positivas de EBP (porque se enviaron antes de la llamada a la función), y las variables locales se ubican en las compensaciones negativas de EBP. (porque fueron asignados en la pila después de la entrada de la función). Es por eso que el EBP se llama puntero de marco , porque apunta al centro del marco de llamada de función .

Al salir, todo lo que tiene que hacer la función es establecer ESP en el valor de EBP (que desasigna las variables locales de la pila y expone la entrada EBP en la parte superior de la pila), luego extrae el antiguo valor de EBP de la pila, y luego la función regresa (colocando la dirección de retorno en EIP).

Usando este ejemplo de wikipedia, en el que DrawSquare () llama a DrawLine (),

(Tenga en cuenta que este diagrama tiene direcciones altas en la parte inferior y direcciones bajas en la parte superior.)

¿Podría alguien explicarme qué ebp y esp están en este contexto?

Por lo que veo, diría que el puntero de la pila apunta siempre a la parte superior de la pila, y el puntero de la base al principio de la función actual. ¿O que?

Edición: me refiero a esto en el contexto de los programas de Windows

edit2: ¿Y cómo funciona eip , también?

edit3: tengo el siguiente código de MSVC ++:

var_C= dword ptr -0Ch var_8= dword ptr -8 var_4= dword ptr -4 hInstance= dword ptr 8 hPrevInstance= dword ptr 0Ch lpCmdLine= dword ptr 10h nShowCmd= dword ptr 14h

Todos ellos parecen ser palabras clave, por lo que toman 4 bytes cada uno. Entonces puedo ver que hay un espacio entre hInstance y var_4 de 4 bytes. ¿Qué son? ¿Asumo que es la dirección de retorno, como se puede ver en la imagen de wikipedia?

(Nota del editor: se eliminó una cita larga de la respuesta de Michael, que no pertenece a la pregunta, pero se editó una pregunta de seguimiento):

Esto se debe a que el flujo de la llamada de función es:

* Push parameters (hInstance, etc.) * Call function, which pushes return address * Push ebp * Allocate space for locals

Mi pregunta (la última, ¡espero!) Ahora es, ¿qué es exactamente lo que sucede desde el instante en que saco los argumentos de la función que quiero llamar hasta el final del prólogo? Quiero saber cómo evolucionó ebp, esp durante esos momentos (ya entendí cómo funciona el prólogo, solo quiero saber qué sucede después de que empujé los argumentos en la pila y antes del prólogo).


En primer lugar, el puntero de la pila apunta a la parte inferior de la pila, ya que las pilas x86 se construyen desde valores de dirección altos hasta valores de dirección más bajos. El puntero de pila es el punto donde la próxima llamada a empujar (o llamada) colocará el siguiente valor. Su operación es equivalente a la declaración C / C ++:

// push eax --*esp = eax // pop eax eax = *esp++; // a function call, in this case, the caller must clean up the function parameters move eax,some value push eax call some address // this pushes the next value of the instruction pointer onto the // stack and changes the instruction pointer to "some address" add esp,4 // remove eax from the stack // a function push ebp // save the old stack frame move ebp, esp ... // do stuff pop ebp // restore the old stack frame ret

El puntero de la base es la parte superior del cuadro actual. ebp generalmente apunta a su dirección de retorno. ebp + 4 apunta al primer parámetro de su función (o al valor de este método de clase). ebp-4 apunta a la primera variable local de su función, generalmente el valor antiguo de ebp para que pueda restaurar el puntero de marco anterior.


Hace mucho tiempo que no hago programación en ensamblador, pero este enlace podría ser útil ...

El procesador tiene una colección de registros que se utilizan para almacenar datos. Algunos de estos son valores directos, mientras que otros apuntan a un área dentro de la RAM. Los registros tienden a usarse para ciertas acciones específicas y cada operando en ensamblaje requerirá una cierta cantidad de datos en registros específicos.

El puntero de pila se utiliza principalmente cuando llama a otros procedimientos. Con los compiladores modernos, un montón de datos se descargarán primero en la pila, seguido de la dirección de retorno para que el sistema sepa a dónde regresar cuando se le diga que regrese. El puntero de pila apuntará a la siguiente ubicación donde los datos nuevos pueden ser empujados a la pila, donde permanecerán hasta que vuelvan a aparecer.

Los registros básicos o los registros de segmentos solo apuntan al espacio de direcciones de una gran cantidad de datos. Combinado con un segundo registro, el puntero Base dividirá la memoria en bloques enormes, mientras que el segundo registro apuntará a un elemento dentro de este bloque. Los punteros de base apuntan a la base de bloques de datos.

Tenga en cuenta que el ensamblaje es muy específico de la CPU. La página a la que he enlazado proporciona información sobre los diferentes tipos de CPU.


Lo tienes bien. El puntero de la pila apunta al elemento superior de la pila y el puntero de la base apunta a la parte superior "anterior" de la pila antes de llamar a la función.

Cuando llama a una función, cualquier variable local se almacenará en la pila y el puntero de pila se incrementará. Cuando regresa de la función, todas las variables locales en la pila quedan fuera del alcance. Para ello, vuelva a colocar el puntero de pila en el puntero base (que era la parte superior "anterior" antes de la llamada de función).

Hacer la asignación de memoria de esta manera es muy , muy rápido y eficiente.


esp significa "Extended Stack Pointer" ..... ebp para "Something Base Pointer" .... y eip para "Something Instruction Pointer" ...... El stack Pointer apunta a la dirección de desplazamiento del segmento de pila . El puntero base apunta a la dirección de desplazamiento del segmento adicional. El puntero de instrucción apunta a la dirección de desplazamiento del segmento de código. Ahora, sobre los segmentos ... son pequeñas divisiones de 64 KB del área de memoria de los procesadores ... Este proceso se conoce como Segmentación de memoria. Espero que esta publicación haya sido útil.


esp es como dices que es, la parte superior de la pila.

ebp lo general, ebp se establece en esp al inicio de la función. Se accede a los parámetros de función y las variables locales sumando y restando, respectivamente, un desplazamiento constante de ebp . Todas las convenciones de llamada x86 definen ebp como preservado entre llamadas de función. ebp sí mismo apunta al puntero base del cuadro anterior, que permite caminar de la pila en un depurador y ver otras variables locales de cuadros para que funcionen.

La mayoría de los prólogos de funciones se parecen a algo como:

push ebp ; Preserve current frame pointer mov ebp, esp ; Create new frame pointer pointing to current stack top sub esp, 20 ; allocate 20 bytes worth of locals on stack.

Luego, más adelante en la función, puede tener un código similar (suponiendo que ambas variables locales son de 4 bytes)

mov [ebp-4], eax ; Store eax in first local mov ebx, [ebp - 8] ; Load ebx from second local

La optimización de omisión de puntero de fotograma o fotograma que puede habilitar eliminará esto y usará ebp como otro registro y acceder a locales directamente desde esp , pero esto hace que la depuración sea un poco más difícil ya que el depurador ya no puede acceder directamente a los fotogramas de pila de la función anterior llamadas

EDITAR:

Para su pregunta actualizada, las dos entradas que faltan en la pila son:

var_C = dword ptr -0Ch var_8 = dword ptr -8 var_4 = dword ptr -4 *savedFramePointer = dword ptr 0* *return address = dword ptr 4* hInstance = dword ptr 8h PrevInstance = dword ptr 0C hlpCmdLine = dword ptr 10h nShowCmd = dword ptr 14h

Esto se debe a que el flujo de la llamada de función es:

  • Parámetros de inserción ( hInstance , etc.)
  • Función de llamada, que empuja la dirección de retorno
  • Empuje ebp
  • Asignar espacio para los locales

Editar Sí, esto es mayormente incorrecto. Describe algo completamente diferente en caso de que alguien esté interesado :)

Sí, el puntero de la pila apunta a la parte superior de la pila (no sé si es la primera ubicación de pila vacía o la última completa). El puntero de base apunta a la ubicación de memoria de la instrucción que se está ejecutando. Esto es en el nivel de los códigos de operación: la instrucción más básica que puede obtener en una computadora. Cada código de operación y sus parámetros se almacenan en una ubicación de memoria. Una línea C o C ++ o C # podría traducirse a un código de operación, o una secuencia de dos o más, dependiendo de lo complejo que sea. Estos se escriben en la memoria del programa de forma secuencial y se ejecutan. En circunstancias normales, el puntero base se incrementa una instrucción. Para el control de programa (GOTO, IF, etc.) puede incrementarse varias veces o simplemente reemplazarse con la siguiente dirección de memoria.

En este contexto, las funciones se almacenan en la memoria del programa en una dirección determinada. Cuando se llama a la función, se inserta cierta información en la pila que permite que el programa encuentre su lugar donde se llamó a la función, así como los parámetros a la función, luego la dirección de la función en la memoria del programa se inserta en la puntero base En el siguiente ciclo de reloj, la computadora comienza a ejecutar instrucciones desde esa dirección de memoria. Luego, en algún momento, REGRESARÁ a la ubicación de la memoria DESPUÉS de la instrucción que llamó a la función y continuará desde allí.


EDITAR: Para una mejor descripción, vea Desarmado x86 / Funciones y Marcos de pila en un WikiBook sobre el ensamblaje x86. Intento agregar información que podría interesarle usar Visual Studio.

Almacenar el EBP de la persona que llama como la primera variable local se llama un marco de pila estándar, y esto se puede usar para casi todas las convenciones de llamadas en Windows. Existen diferencias, ya sea que el llamante o el destinatario de la llamada desasignen los parámetros pasados ​​y qué parámetros se pasen en los registros, pero estos son ortogonales al problema del marco de pila estándar.

Hablando sobre los programas de Windows, probablemente utilice Visual Studio para compilar su código C ++. Tenga en cuenta que Microsoft utiliza una optimización llamada Frame Pointer Omission, que hace que sea casi imposible recorrer la pila sin usar la biblioteca dbghlp y el archivo PDB para el ejecutable.

Esta omisión del puntero de marco significa que el compilador no almacena el EBP anterior en un lugar estándar y usa el registro de EBP para otra cosa, por lo tanto, tiene dificultades para encontrar el EIP de la persona que llama sin saber cuánto espacio necesitan las variables locales para una función determinada. Por supuesto, Microsoft proporciona una API que le permite realizar caminatas por la pila incluso en este caso, pero buscar la base de datos de la tabla de símbolos en archivos PDB toma demasiado tiempo para algunos casos de uso.

Para evitar FPO en sus unidades de compilación, debe evitar el uso de / O2 o debe agregar explícitamente / Oy- a los indicadores de compilación de C ++ en sus proyectos. Probablemente se vincule con el tiempo de ejecución de C o C ++, que utiliza FPO en la configuración de la versión, por lo que tendrá dificultades para realizar caminatas en la pila sin el dbghlp.dll.