Stack in mind


博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收**藏 ^ _ ^ !

Basic Stack Terminology

Stack (Stack)
is similarly called stack or stack in Chinese, and is a linear storage structure. It is a data structure that can only perform operations (store, search, insert, delete) at the top of the stack, and has the LIFO (last in, first out) feature. The stack in the memory structure and the data structure have two different meanings and fields.

Stack top and stack bottom The end that
allows element insertion and deletion is called the stack top, and the other end is called the stack bottom.

The push
to insert elements into the stack operation, called into the stack, also known as push, push

Stack
deletion of the top element, also called a stack.

Stack data structure

Use a custom stack to simply describe the data structure of the stack, and its data can be stored in two ways: sequential storage and chain storage.
1) Sequential storage structure is to use array to realize stack

/**
 * @author :罗发新
 * time  :2020/6/27 0027  14:58
 * email :[email protected]
 * desc  :
 */
class ArrayStack {
    
    

    private int[] stack;

    /**
     * 默认分配空间
     */
    private final int DEFAULT_SIZE = 10;

    /**
     * 当前元素的数量
     */
    private int currentCount;

    /**
     * 默认大小构造
     */
    public ArrayStack() {
    
    
        stack = new int[DEFAULT_SIZE];
        currentCount = 0;
    }

    /**
     * 指定大小构造
     *
     * @param size 创建的数组大小
     */
    public ArrayStack(int size) {
    
    
        stack = new int[size];
    }

    /**
     * 返回当前栈元素数量
     *
     * @return 当前数组Count
     */
    public int getSize() {
    
    
        return currentCount;
    }

    /**
     * @return 获取栈顶元素
     */
    public int getTop() {
    
    
        return stack[getSize()];
    }

    /**
     * 入栈
     *
     * @param element 元素
     * @return 返回是否
     */
    public boolean push(int element) {
    
    
        if (currentCount == stack.length) {
    
    
            return false;
        } else {
    
    
            stack[getSize()] = element;
            currentCount++;
            return true;
        }
    }

    /**
     * 出栈
     *
     * @return 退出
     */
    public int pop() {
    
    
        int result;
        if (currentCount == 0) {
    
    
            throw new RuntimeException();
        } else {
    
    
            result = stack[getSize() - 1];
            currentCount--;
            return result;
        }
    }
}

2) Chained storage structure, ready-to-use linked list implementation stack (single linked list)

class ListNode {
    
    
    /**
     * 节点数据值
     */
    int value;
    /**
     * 指向下一节点的next指针
     */
    ListNode next;

    public int getValue() {
    
    
        return value;
    }

    public void setValue(int value) {
    
    
        this.value = value;
    }

    public ListNode getNext() {
    
    
        return next;
    }

    public void setNext(ListNode next) {
    
    
        this.next = next;
    }

    public ListNode(int value, ListNode next) {
    
    
        this.value = value;
        this.next = next;
    }
}


class LinkedStack {
    
    
    /**
     * 指向栈顶元素的top指针
     */
    private ListNode top;

    /**
     * @return 获取栈顶元素
     */
    public int getTop() {
    
    
        return top.value;
    }

    /**
     * 入栈
     *
     * @param value 插入的值
     */
    public void push(int value) {
    
    
        ListNode listNode = new ListNode(value, null);
        if (top == null) {
    
    
            top = listNode;
        } else {
    
    
            listNode.next = top;
            top = listNode;
        }
    }

    /**
     * 出栈
     *
     * @return 出栈的数据
     */
    public int pop() {
    
    
        int result;
        if (top == null) {
    
    
            throw new RuntimeException();
        } else {
    
    
            result = top.value;
            top = top.next;
            return result;
        }
    }
}

Stack in memory

Why the stack exists

In memory, the stack is a linear data structure. The parameters of the function, the local variables of the function, the value of the register (used to restore the register), the return address of the function, and the data used for structured exception handling are organized in an orderly manner to form a stack frame. Stack frames.

In programming, variables are often divided into local variables and global variables. Local variables only take effect when the function in which they are called, and become invalid once the function returns, so the life cycle of many local variables is much lower than the running cycle of the entire program.

If you allocate a different space for each local variable, let them be located with a certain address like global variables, then there will be some problems:

  1. After the function is called, the local variable becomes invalid, and the allocated address is useless, which will greatly reduce the utilization of the memory space.

  2. When a recursive call occurs, there will be a function that has not yet returned. Through this multiple calls, local variables with the same name will have different values, and these values ​​must be stored in memory at the same time, and they cannot affect each other. So they must be stored in different addresses, so that a lot of space will be wasted when a recursion is completed.

  3. For function parameters, they are also very similar to local variables. They cannot be located with fixed addresses like global variables.

The solution to the above problem is to store data such as local variables and formal parameters in a special structure, which is the stack . This stack for storing function parameters and local variables is also called the running stack.

Stack in memory

In the JVM, the stack has a part of the memory space, and it accesses the memory in a special way. The heap and stack mentioned here are not the heap and stack in the data structure, but the stack in the heap area and the stack area.

  • The stack of the program is directly supported by the processor. The stack is extended from high address to low address in memory (this is different from the custom stack extending from low address to high address)

  • In a 32-bit system, the size of each data unit on the stack is 4 bytes. Data less than or equal to 4 bytes, such as bytes, words, double words and booleans, occupy 4 bytes in the stack. Data larger than 4 bytes occupies an integer multiple of 4 bytes in the stack.

  • The ESP register always points to the top of the stack. When the PUSH command is executed to push data onto the stack, ESP is reduced by 4, and then the data is copied to the address pointed to by ESP; when the POP command is executed, the data pointed to by ESP is first copied to the memory address/ In the register, then ESP increases by 4.

  • The EBP register is used to access the data in the stack, and it points to a certain location in the middle of the stack. The parameter address of the function is higher than the value of EBP, and the local variable address of the function is lower than the value of EBP, so the parameter or local variable is always accessed by adding and subtracting a certain offset address from EBP, for example, to access the first part of the function One parameter is EBP+8.

Stack allocation

Talking about the stack in a C language program
1) Every time we boot up, the system initializes the stack pointer (SP). The initial method is also very simple, we can see in the boot_load code: ldr sp, =4096
statement is to make the SP pointer point to such an address.
2) Note that the address pointed to by the stack pointer SP is an address in memory, not an address on the cpu chip. Memory resources are much more abundant than CPU resources, so SP can have a lot of room for growth, which is also the premise for C language to write complex programs.

Function call direction

We know that the stack growth direction is different in different systems. In Windows, it grows downward, but the structure of the stack determines that it must be a first-in-last-out model.
So the process of calling our function is similar. The function called first always returns last, and the function called last returns first.
The way the stack is popped determines the return process of the function, and the growth space of the stack supports the complexity of function nesting.

Run stack

The data in the running stack is divided into a stack frame, and each stack frame corresponds to a function call. Therefore, the stack frame contains the formal parameter values ​​in this function call, local variable values, some control information, and temporary data (such as the intermediate value calculated by complex expressions, and the return value of some functions).

Principle of running stack:

  1. Every time a function call occurs, a stack frame will be pushed into the running stack, and after the function call returns, the corresponding stack frame will be popped out.

  2. During the execution of a function, it can directly and randomly access the data in its corresponding stack frame, that is, the data of the stack frame at the top of the running stack (the stack frame of the function being executed is always in the running stack Top).

  3. When a function calls other functions, it is necessary to set the actual parameters for the function it calls. The specific method is to push the actual parameters into the stack before calling. This part of the space in the running stack is the calling function and the called function can be directly The access, that is, the formal combination of parameters, is accomplished through this public space.

  4. Although the addresses of the formal parameters and local variables of each function are uncertain when they are called, their addresses are determined relative to the address of the top of the stack, so that the function can be located indirectly through the address of the top of the stack Formal parameters and local variables.

Stack Frame

Function calls are often nested. At the same time, there will be multiple function information () in the stack. Each unfinished function occupies an independent continuous area, called a stack frame.

Stack frame characteristics

The stack frame has the following characteristics:

  1. One stack frame corresponds to one function call. At the beginning of the function, the corresponding stack frame has been completely established. When the function exits, the entire function frame will be destroyed. All local variables have been allocated space when the function frame is created, instead of being created and destroyed continuously with the execution of the function

  2. The stack frame stores function parameters, local variables and data needed to restore the previous stack frame.

  3. The stack frame is the logical fragment of the stack. The logical stack frame is pushed onto the stack when the function is called, and the logical stack frame is popped from the stack when the function returns.

  4. The compiler uses the stack frame to make the allocation and release of function parameters and local variables in the function transparent to the programmer.

  5. Before the compiler transfers control to the function itself, it inserts specific code to push the function parameters into the stack frame, and allocates sufficient memory space for storing local variables in the function.

  6. One advantage of using stack frames is to make recursion possible, because each recursive call to a function will be assigned a new stack frame to the function, so that the current call and the last call are cleverly separated.

  7. The boundary of the stack frame is defined by EBP and ESP, and the accurate value of the local variable space allocated on the function stack frame is determined through EBP analysis

  8. The stack frame is a runtime concept. If the program is not running, there is no stack and stack frame.

  9. The creation and cleaning of the stack frame are all done by the function caller Caller and the called function Callee.

Stack frame construction, value transfer and destruction

The construction of stack frame
Take the following code as an example:

public static void main(String[] args) {
    
    

        int result = add(3, 4);
        int b=result*10;

    }

    private static int add(int a, int b) {
    
    
        int c = A*2;
        int d = b*3;
        return add2(c,d) + c + d;
    }

    private static int add2(int c, int d) {
    
    
        return d + c ;
    }
   
  • Before calling the add() function, the stack frame corresponding to main and the previous function already exists in the stack.
    Insert picture description here

  • When the add function is called, first push a=3, b=4 onto the stack. Generally speaking, parameters are pushed into the stack from right to left, so the stacking order is 4 and 3.
    Insert picture description here

  • Then push the instruction address 0x00***** of the function add onto the stack
    Insert picture description here

  • When the address of the add() method is pushed onto the stack, the value in the EBP register needs to be pushed onto the stack at this time. At this time, the value in EBP is still in the main function, and it can be used to access the local variables and parameters of the main function. Pushing in the EBP value is to retrieve the response when the add() exits later. After the value of EBP is stored on the stack, a new value is assigned to EBP at this time, and the new value points to the memory address of the previous EBP value.
    Insert picture description here

  • Next, the add() function allocates addresses for local variables. Instead of stacking them one by one, they pass a certain rule ESP=ESP-0x00F3 (this value is only an example and needs to be determined according to certain rules), so that an address space is allocated to store local variables. The local variable addresses may be discontinuous and have gaps.

  • Then the value in the general register is pushed onto the stack, then a complete stack frame is established.
    Insert picture description here
    +In the same way, add add2() to the stack to get the final complete stack as shown in the figure.
    Insert picture description here
    Transfer value of
    stack frame How to transfer the return value in the stack frame, the function caller and the called function have the "convention" about the return value storage, as follows:

  1. First, if the return value is equal to 4 bytes, the function will assign the return value to the EAX register and return through the EAX register. For example, the return value is byte, word, double word, boolean, pointer and other types, all of which are returned through the EAX register.

  2. If the return value is equal to 8 bytes, the function will assign the return value to the EAX and EDX registers and return through the EAX and EDX registers. EDX stores the upper 4 bytes and EAX stores the lower 4 bytes. For example, a structure whose return value type is __int64 or 8 bytes is returned through EAX and EDX.

  3. If the return value is double or float type, the function will assign the return value to the floating-point register and return it through the floating-point register.
    Insert picture description here

The caller will press the leftmost parameter, and then press a pointer, let's call it ReturnValuePointer, ReturnValuePointer points to an unnamed address in the local variable area of ​​the caller, this address will be used to store the return value of the callee. The steps are:

  1. When the function returns, callee copies the return value to the address pointed to by ReturnValuePointer, and then assigns the address of ReturnValuePointer to the EAX register.

  2. After the function returns, the caller finds the ReturnValuePointer address value through the EAX register, and then finds the return value through ReturnValuePointer, and finally, the caller copies the return value to the local variable responsible for receiving.

You may have this question. After the function returns, the corresponding stack frame has been destroyed, and the ReturnValuePointer is in the stack frame. Shouldn't it be destroyed?
Yes, the stack frame is destroyed, but the address to save the return value is assigned to the general-purpose register EAX, the return value still exists in the memory, when EAX copies the address value to a local variable.

Destruction of the stack frame
When the function assigns the return value to some registers or copies it somewhere on the stack, the function starts to clean up the stack frame and prepares to exit. The order in which the stack frame is cleaned up is exactly the opposite of the order in which the stack is built, and the one that is put into the stack last is out of the stack.

  1. If an object is stored in the stack frame, the destructor of the object will be called by the function.

  2. Pop the value of the previous general-purpose register from the stack and assign the value to the general-purpose register again.

  3. ESP adds a certain value to reclaim the address space of the local variable (the added value is the same size as the address allocated to the local variable when the stack frame is created).

  4. Pop the previous EBP register value from the stack, assign the value to the EBP register, and re-point to the bottom of the previous stack frame

  5. Pop the return address of the function from the stack and prepare to jump to the return address of the function to continue execution.

  6. ESP adds a certain value to recover all parameter addresses.

The first 1-5 are all done by callee. And Article 6 is done by the caller or callee is determined by the calling convention used by the function. The calling convention is not explained here, please check the information yourself.

EBP and ESP

EBP
stack frame base address pointer, EBP points to the bottom (high address) of the current stack frame, and the position is fixed in the current stack frame.
1) EBP is used to access parameters and local variables. As shown in the figure above, it is found that the parameter address is higher than the EBP value, and the local variable address is lower than the EBP value.
2) The address offset value of each parameter and local variable relative to EBP is fixed, and the parameter and local variable are accessed through the EBP value plus a specific offset, as shown in the add stack frame above, EBP+8 is the first A parameter value, EBP-4 is the address of the first local variable
3) Each EBP address always points to the EBP pointer address of the previous stack frame

The ESP
stack pointer points to the top (lower address) of the current stack frame. When the program is executed, the ESP will move as data is pushed into and out of the stack.

The format of phsh and pop instructions
push register: push the data in a register onto the stack. Change SP first, then transfer the
pop register to SS:SP : use a register to accept the popped data. First read SS:SP data, then change SP

博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收**藏 ^ _ ^ !

Related Links:

  1. I have a pile in my mind: https://blog.csdn.net/luo_boke/article/details/106928990
  2. There is a tree in the heart-basic: https://blog.csdn.net/luo_boke/article/details/106980011
  3. Analysis of common sorting algorithms: https://blog.csdn.net/luo_boke/article/details/106762372

Guess you like

Origin blog.csdn.net/luo_boke/article/details/106982563