Function reference returns underlying analysis - stack frame creation and destruction

Reference: can be understood as giving an alias to a variable, or sharing a space with the variable

Application: passing parameters by reference, returning by reference


Table of contents

1. Creation and destruction of function stack frames

(1) Preparation work

(2) Function stack frame creation

(3) Function stack frame destruction

(4) Summary

2. Reference return

(1) Test one:

(2) Test 2:

3. Comparison of efficiency between return by reference and return by value

4. Scope of application of reference return


Here we focus on reference return. Before we talk about reference return, let’s talk about the creation and destruction of function stack frames.

When the function stack frame is created, the push of the registers ebx and esi will be ignored

1. Creation and destruction of function stack frames

(1) Preparation work

Let’s first prepare a simple piece of code

#include <stdio.h>

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

int main(){
    int a = 10;
    int b = 20;
    int ret = Add(a,b);
    return 0;
}

(2) Function stack frame creation

================ Preparation before calling Add function ================

When the program starts running, the OS opens up a space for the main function on the stack, and then stores local variables on the stack

When it’s time to start executing the Add function, read the actual parameters from right to left , put the copy of the actual parameters into the register push to the top of the stack

To jump to the Add function below, you need to first write down the address of the next instruction 0CC2145 , which is the address of the code ''return 0;''. Even if you leave home, you still have to remember your home address so you know where to go next time you come back.

So far, the preparations for entering the Add function have been completed

=============== Execute the Add function ===============

Next, the OS will open up a space for the Add function on the stack and push it to the top of the stack (the size of the stack depends on how many variables there are in the function)

In the Add function, int z = x+y, a variable z is declared, so it is necessary to open up space on the stack of Add to store z

Since the parameters x and y are used, the program will go to the position of ebp+8, which is the ecx register, to get the value.

Put it into the ebx register, then go to the position of ebp+12, that is, the eax register, add it to ebx ====> ebx += eax

Here comes the most critical part! ! The next step is the key to distinguish between return by reference and return by value

(3) Function stack frame destruction

Now that z is available, how to return z? mov is an assembly instruction, which stands for assignment. In fact, it assigns the value of z to the register eax, and then starts to destroy the space and return it to the memory. The destruction here is not real destruction, but just moves the esp pointer so that this area does not belong to the current process. , the next time other functions are called, directly cover this area to create a function stack frame.

But whether the data will be cleared depends on the compiler. The VS compiler will not clear the data. Only the address is stored on the stack, and the data is placed in the constant area.

Pay attention to where esp points.

According to the recorded address, the assembly instruction ret returns the program to the next position of the Add function, which is where the return 0 statement is located.

At this time, it is necessary to continue popping the stack. After popping the stack, open up a space for ret on the stack of main, and assign the result stored in the previous eax register to ret.

 

(4) Summary

In general, the Add function stores the result in the eax register and assigns it to ret when it comes back.

 But it wasn't always like this,

If the value passed is relatively small, occupying only 4 bytes, it is stored in the register first and then assigned to ret.

If the passed value is relatively large, greater than 4 bytes, it is stored in a temporary variable , which is opened on the caller's stack, that is to say, the main function calls the Add function, and the Add function returns a value to main. This temporary variable is created on the main function

2. Reference return

(1) Test one:

#include <stdio.h>

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

int main() {
    int a = 10;
    int b = 20;
    int& ret = Add(a, b);
    printf("ret的值:%d\n", ret);
    return 0;
}

The test results are as follows:

The Add function returns a reference to z, but after popping out of the stack, z is destroyed. The destruction here just means that the stack area created by the Add function just now does not belong to the current process, but the space is still there

(2) Test 2:

#include <stdio.h>

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

int main() {
    int a = 10;
    int b = 20;
    int& ret = Add(a, b);
    printf("ret的值:%d\n", ret);
    Add(100, 200);
    printf("ret的值:%d\n", ret);
    return 0;
}

We execute Add again, but this time we do not assign a value to ret.

 We will find that ret has been secretly changed:

  • The first time the Add function is executed, a reference to z is returned.
  • The second execution of the Add function still returns the reference of z, but since ret is an alias of z and shares a space with z, when the second execution changes z, it also changes ret

Therefore, try to avoid returning references to local variables! !

3. Comparison of efficiency between return by reference and return by value

#include <stdio.h>
#include <time.h>
#include <iostream>
using std::cout;
using std::endl;

struct A { int a[10000]; };
A a;

A Test1()
{
    return a;
}

A& Test2()
{
    return a;
}

int main() {

    //传值返回
    size_t begin1 = clock(); 
    for (size_t i = 0; i < 100000; ++i) 
        Test1();
    size_t end1 = clock();

    //传引用返回
    size_t begin2 = clock();
    for (size_t i = 0; i < 100000; ++i)
        Test2();
    size_t end2 = clock();

    cout << "传值返回耗费时间: " << end1 - begin1 << endl;
    cout << "传引用返回耗费时间: " << end2 - begin2 << endl;
    return 0;
}

==》When the data occupies a small byte size, there is not much difference between passing by value and passing by reference.

==》When the data occupies larger bytes, it is better to pass the reference

References can reduce copying. There is no need to copy values ​​to registers or temporary variables, which improves running speed to a certain extent.

4. Scope of application of reference return

When the function returns, it goes out of the function scope. If the returned object is still there and has not been returned to the system (static variables, global variables), you can use reference return

When the function returns, it goes out of the function scope and the return object has been returned to the system (local variable). At this time, you must use return by value.

===》Try to avoid returning references to local variables

Guess you like

Origin blog.csdn.net/challenglistic/article/details/123537888#comments_28086206