In-depth understanding of the runtime stack (C language)

runtime stack

The stack is a data structure: we can store data into this structure, and we can also pop data in this structure. The characteristics of this structure are: the data pushed later is popped first, and the data pushed first is popped.

In computer systems, the stack is a dynamic memory area with the above attributes. Programs can push data onto the stack or pop data from the stack. The stack increases during the push operation, and the stack decreases during the pop operation.

This memory management principle is also used in C language procedure calls - first in, last out

Memory layout in a computer system:

insert image description here

It can be seen that the stack grows from high address to low address, the top of the stack is at the low address, and the bottom of the stack is at the high address.

function stack frame

Every time a program calls a function, it creates a space in the stack area, and this space is called the stack frame of the function.

This space generally includes the following information:

  1. The return address and parameters of the function
  2. Temporary variables: including non-static local variables of functions and other temporary variables generated by the compiler
  3. saved register

insert image description here

When a running program calls another function, it will enter a new stack frame. The stack frame of the original function is called the caller's frame, and the new stack frame is called the current frame. After the called function finishes running, the current frame is all recycled and returns to the caller's frame.

For example: when function A calls function B, it pushes the return address onto the stack. We treat the return address as part of the stack frame of function A because it stores the state related to A.

Note: espThe register always points to the top of the current stack frame ( espthe address of the top of the stack is saved).

insert image description here

You can see that this mechanism of pushing the return value onto the stack allows the function to return to the correct place in the program later.

Little knowledge:
We sometimes get "hot" when debugging, what is the reason for this?
In fact, it is because we will assign the stack frame to 0xcccccccc after creating the function stack frame in the debug case,
and the Chinese character encoding of two consecutive 0xcc is hot, so 0xcccc is regarded as "hot" as text.

Registers and Machine Instructions

To understand the stack frame of a function, you should understand some concepts about registers and simple machine instructions.

register:

A CPU contains a set of registers that can store integer data and pointers, each register has a special purpose

On IA32-bit systems, these registers can store 32-bit values, and on 64-bit systems, these registers can store 64-bit values

Below are the registers in the 32-bit case

name use
eax return value
ebp stack bottom pointer
esp stack pointer
rdi
rsi
rdx
rcx

Among the above registers, the most special one is espthat this register is called the stack pointer, also called the stack top pointer (which stores the address of the top of the stack), which is used to indicate the end position of the stack at runtime.

The ebpregister is used to point to the bottom of the stack frame (the address of the bottom of the stack is stored). Call it the bottom of the stack pointer.

machine instructions

At this point we need to understand some simple machine instructions and assembly code (the part related to stack frames)

Arithmetic operations :

instruction Effect describe
ADD S,D D = D + S addition
SUB S,D D = D - S subtraction
lea S,D D <-- &S load valid address
MOV S,D D <-- S transfer (copy)

Pop and push data on the stack:

instruction Effect
push push the stack
pop pop stack

pushThe function of the instruction is to push data onto the stack and at the same time change the stack pointer:

Each pushoperation decrements the stack pointer

On the system: the stack pointer is decremented 32位every time ;push4

On the 64位system, the pushstack pointer is decremented each time8

popThe function of the instruction is to pop data from the stack and change the frame pointer at the same time

A pop will increase the stack pointer

In the 32位system: each time popthe stack pointer will be incremented4

In the 64位system: each time popthe stack pointer will be incremented8

insert image description here

program counter

Usually called a PC, giving the address in memory of the next instruction to be executed

insert image description here

transfer of control

In fact, control transfer means that after we call another function in one function, we start to execute the operation of the called function. It can also be understood as setting the value of PC to the address of the first statement of the called function.

The instructions required to transfer control are:

instruction Effect
call Call functions
ret return from function

instruction call—calls a function

Step 1: Push the address of the next instruction to be executed onto the stack

The second step is to set the program counter to the starting position of the called function

insert image description here

Instruction ret - return from a function

Pop the address from the stack and set the value to pc to that address
insert image description here

It can be found that this mechanism of pushing the return address onto the stack allows the function to return to the correct place in the program later

data transmission

When we call a procedure, we often need to pass some data as parameters to the procedure, and the procedure may also return a value. How can we relate the data between the two processes?

passing of parameters

When one procedure calls another procedure, the code in the first procedure must first assign the parameters to the appropriate registers.

Passing the return value

The return value of a function is usually eaxstored in a register

When the latter procedure returns to the former procedure, the content to be returned will be saved in the register . After the function call ends, the code in the former procedure can obtain the return value eaxby accessing the value in the registereax

Example: The whole process of creating and destroying function stack frames

We use the following code snippet to analyze the creation and destruction of function stack frames

In this code snippet, the main function calls a summation function ADD:

#include<stdio.h>

int ADD(int x,int y)
{
    
    
    int z =  x + y;
    return z;
}

int main()
{
    
    
    int a = 10;
    int b = 20;
    int sum = 0;
    sum = ADD(a,b);
    return 0;
}

Here is the assembly code for these two functions:
insert image description here

What we need to know is that the main function is also called by other functions, so when the mainstack frame has not been created for the function in the stack, the espregister points to the top of the stack of the previous function:
insert image description here

After calling mainthe function, start maincreating a function stack frame for the function:

Push ebpthe address to the stack, and then open up a space:

insert image description here

Then push the registers that need to be saved onto the stack and assign values ​​to the newly opened up.

insert image description here

In this way main, the function stack frame of the function is created, and then mainthe statements in the function are executed:

int a = 10;
int b = 20;
int sum = 0;
sum = ADD(a,b);

insert image description here

When the execution arrives sum = ADD(a,b), the parameters are passed first and the return address is pushed onto the stack:

insert image description here

Push return value:

insert image description here

After that ADD, the function stack frame of the function can be created (the preparation process mainis very similar to the function creation process):

insert image description here

Execute ADDthe operations in the function:

insert image description here

After calling ADD, the stack frame of the function will be destroyed.

insert image description here

After that, the main function gets the return value: the calculation and storage are performed, and then the main function is called, and the stack frame of the main function is destroyed.
insert image description here

summary

We use some questions to reflect the details in the runtime stack:

How are local variables created?

The creation of local variables first allocates the stack frame space for the function where the bit is located. A part of the space is initialized in the stack frame space, and then a little space is allocated to the local variables in the stack frame.

Why is the value of a local variable random when it is not initialized?

When the stack frame of the function is created, the initialized space stores random values. If the local variables are not initialized when they are created, the random values ​​of the space will not change. If they are initialized when they are created local variable, the initialized value will overwrite the random value.

How do functions pass parameters? What is the order in which the parameters are passed?

When the function has not been called, the actual parameters have been pushed from right to left on the stack. When the function is actually entered, the pointer offset in the function is retrieved to find the formal parameter.

The relationship between formal parameters and actual parameters?

A formal parameter is a temporary copy of an actual parameter, and changing the formal parameter does not affect the actual parameter.

How is the result of a function call returned?

Before the call, the address of the next instruction of the call instruction has been remembered, and the previous function of this function ebphas been stored in it. When the function is used to return, the ebplast function call can be found by popping up. , ebpand then the address that can be found when the pointer goes down esp, so that it has returned to the stack frame space of a function in Korea, and because the address of the next instruction to call is remembered before the call, so that we can call the function when we call it. Return, the return value is brought back by saving it in a register.

Guess you like

Origin blog.csdn.net/cainiaochufa2021/article/details/123693134