Http://blog.csdn.net/zhanglianpin
We know that C language is a high-level language. The so-called high-level language is a program that can run on a specific platform only after translation. Compiling a program is a complicated program. It must compile and link the advanced language to become a program that can run on a specific platform. There is a lot of knowledge related to the operating system and specific hardware platform. If you want to find out how to compile the program, please learn the compilation principle. For a book, refer to linkers_and_loaders.
Here we will only describe the C language running environment and its relationship with the stack. Let's take a look at some concepts of the C language from the assembly language and underlying hardware, and how the C language uses stacks to call the control process.
First, let's talk about the stack:
Stack is such a structure: the skill is a continuous memory space. How to use such a memory space can be regarded as the actual function of the stack, first, specify the base address of the continuous space, and then place the data from the address in sequence. It also starts from the top. Managing this bucket according to the above solution plays the role of stack. Because the frequency of stack usage is too high, there is a dedicated operation stack command at the computer assembly level. Including push and pop.
In fact, the stack has some logical classifications:
It is divided:
1. Full Stack: the stack pointer SP points to the last element in the stack. That is, SP first minus one (plus one) and then re-enters the stack.
2. Empty Stack: the stack pointer points to the next element of the last stack element. That is to say, first import the stack SP and then subtract one (or add one ).
It can be used from the high address or from the low address:
1. incremental Stack: the address at the beginning of the stack is a low address, which increases to the high address. Just like a water cup (assuming the above address is large), the opening is a large address, starting from the bottom of the cup water. Draw a picture by yourself. I'm just a little lazy.
2. Decrease Stack: the address at the beginning of the stack is a high address, which increases progressively toward the low address. Just like the water cup I just mentioned, it is now a small address. It is used from a large address and goes down, which is equivalent to a cup mouth down. When we use it, we press the water up a little bit. Haha, but such a cup is useless. But it still works in the memory.
Based on the two classification methods, we can obtain four stack types, while ARM920T uses a descending full stack.
The following describes how to use stacks to control the function call process during C language runtime.
Let's think about it. Function calling is used when we write C language, and sometimes many functions are nested. Some functions also require parameters and return values. How to deal with parameters and return values of each function, and return to that place when each function completes its work. These are all problems to be solved. Of course, the easiest thing to think of is to save the current state of my function before calling the jump. What to save? The next instruction after the function is returned, what data does my function need. And where to save the information? These are the problems we need to solve. In addition, you not only need to save the information, but also the order of the information. Because function calls are sequential, you call B as a and B call C again. After C is executed, B is returned. After B is executed, A is returned. Oh, there is order.
We try to solve the problem one by one. Of course, the stack policy is perfect for others. We just want some more concise but imperfect methods that can be easily remembered, it also shows how good people's strategies are.
To call a function, we can save the return address to some places. Of course, the programmer knows where it is? I also know the order, and then return it in order, but it is too tired to do this. Besides writing programs, I have to remember these things. Well, it's definitely not good. You can consider passing parameters in this way by using a specially defined register for passing parameters. Line, but there is a defect. If many parameters are passed or changed, it is not easy to use register to transmit parameters. In addition, when we have an operating system, we often require the code generated by the compiler to be reentrant, that is, to ensure the relative independence of code and data. A function is called twice and has two parameter environments. How do we do it now. The answer is stack.
How to use it:
When a function is called, The stack not only saves the return address of the function, but also saves the parameters and return values required by the function in the stack.
That is, every function has a stack that stores some information. First, let's talk about the concept of stack frames. The entire set of parameters to be saved during the function call process, including the returned address called a stack frame.
As shown in, we take this figure as an example to analyze the application of the stack in function calls. Function p has two parameters: X1 and X2. function p calls function Q and has two parameters. The first frame stored in the stack frame is the address of the previous stack frame. The current stack frame address is the base value of the currently used stack frame, using this pointer can easily find the variables and parameters required by the function. Why do the first address of each shot need to save the address of the previous stack frame and the original intention of saving the return address? When the current stack frame is used up, removes the address of the previous stack frame saved in the current stack frame and restores the previous stack frame.
The return address. If the function returns a value, the return value is placed under the return address. Then apply for space for the variables required by the function.
The following describes how function p calls function Q. When call Q (Y1) is executed, a new stack frame is created for function Q. The specific process is to first save the address of the second frame, if there is a return value, allocate a bucket for the returned value and save the return address. Then allocate space for Y1 and initialize it as the parameter given when Q is called. Then, allocate the space Y2 of another parameter, which is used for calculation within the function.
In any State, there is a FP pointer to the current stack frame, which is used to save the address of the current stack frame. How can this value be ensured to be the current one, let's talk about the Q function first. It is to save the stack pointer SP first, and then save FP. Then change the FP value to the sp-4, because we know that the first frame of each stack is saved as FP.
In fact, the whole process is dynamic. I say it is dynamic because the SP pointer has been moving fast. Therefore, you must save the SP at the beginning of each frame. Then place FP at the address minus 4. When all the stack frames pop up, the saved FP will be restored to the FP you saved. FP always represents the bottom of the current stack frame. It is also the only constant base address used to find other variables.
Now let's look at a program written in C language under arm and then compiled into an assembly language to analyze its stack application. (C language program written in linux2.4 kernel, compiled by the arm-linux-gcc3.4.1 compiler)
The C language source code is as follows:
# Include <stdio. h>
Int max (INT, INT );
Int main (INT argc, char * argv [])
{
Int A = 3, B = 5;
Max (A, B );
Return 0;
}
Int max (int x, int y)
{
If (x> Y)
Return X;
Else
Return y
}
The function is very simple. In the main function, call an external function max to use the number with a large value in two numbers and return the result.
The following describes how Arm Assembly implements these functions and how to use the intermediate stack.
. File "Max. c"
. Text
. Align 2
. Global main
. Type main, % Function
Main:
@ ARGs = 0, pretend = 0, frame = 16
@ Frame_needed = 1, uses_anonymous_args = 0
MoV IP, SP
Stmfd SP !, {FP, IP, LR, PC}
Sub FP, IP address, #4
Sub sp, SP, #16
STR r0, [FP, #-16]
STR R1, [FP, #-20]
MoV R3, #3
STR R3, [FP, #-24]
MoV R3, #5
STR R3, [FP, #-28]
LDR r0, [FP, #-24]
LDR R1, [FP, #-28]
BL Max // start to call the max Function
MoV R3, #0
MoV r0, r3
Sub sp, FP, #12
Ldmfd sp, {FP, SP, PC}
. Size main,.-Main
. Align 2
. Global Max
. Type Max, % Function
MAX:
@ ARGs = 0, pretend = 0, frame = 12
@ Frame_needed = 1, uses_anonymous_args = 0
MoV IP, SP
Stmfd SP !, {FP, IP, LR, PC}
Sub FP, IP address, #4
Sub sp, SP, #12
STR r0, [FP, #-16]
STR R1, [FP, #-20]
LDR R2, [FP, #-16]
LDR R3, [FP, #-20]
CMP R2, R3
Ble. l3
LDR R3, [FP, #-16]
STR R3, [FP, #-24]
B. l2
. L3:
LDR R3, [FP, #-20]
STR R3, [FP, #-24]
. L2:
LDR r0, [FP, #-24]
Sub sp, FP, #12
Ldmfd sp, {FP, SP, PC}
. Size Max,.-Max
. Ident "GCC: (GNU) 3.4.1"
The above code is long and tells everyone a password. In fact, the originally mentioned main () function is actually a common function, but it is called by more advanced Dongdong, the common function you write cannot be called. We will focus on calling the max () function from the main () function, regardless of the main () function. Red marks the start of the function call. We can observe that there are two pieces of code before calling Max (). This is to pass the parameters A and B when calling Max () to R0 and R1, at max () the function is then retrieved from R0 and R1, which implements the main function () and max () function parameter passing mechanism. Of course, the parameter passing here is the register parameter passing. Well, why not use the stack parameter passing? This is related to the process call specification during arm programming, in arm programming, there is a call specification called atpcs. We need to follow this specification for programming. For more information, see atpcs.pdf and atpcs overview ). One simple rule is that R0 ~ is used for less than four parameters ~ When there are more than four parameters in the four registers of R3, the remaining word base is sent to the data stack. The order of the incoming stack is the same as that of the parameter, that is, the last word data first enters the stack. There are only two parameters here, so only register parameters are used. The following analysis jumps to the max post-assembly language for work.
Next let's take a look at the use of the stack frame of arm's assembly language.
MAX:
@ ARGs = 0, pretend = 0, frame = 12
@ Frame_needed = 1, uses_anonymous_args = 0
MoV IP, SP // first save the sp value of the stack frame
Stmfd SP !, {FP, IP, LR, PC} // save relevant information
Sub FP, IP address, #4 // reset FP to point it to the current stack frame
The following is the application space and related computing
Sub sp, SP, #12 // three integer variables are applied, two of which are local variables A and B, and the other is used to store the returned values.
STR r0, [FP, #-16]
STR R1, [FP, #-20] // Save the parameters passed through the Register to the stack space.
LDR R2, [FP, #-16] // The following is a comparison of the two big and small code implementations. If you want to know what is going on, analyze it by yourself.
LDR R3, [FP, #-20]
CMP R2, R3
Ble. l3
LDR R3, [FP, #-16]
STR R3, [FP, #-24]
B. l2
. L3:
LDR R3, [FP, #-20]
STR R3, [FP, #-24]
. L2:
LDR r0, [FP, #-24] // place the return value in R0. In atpcs, the return value of the function is specified in R0.
Sub sp, FP, #12 // clear the bucket where you allocate local variables and return values
Ldmfd sp, {FP, SP, PC} // restores the information of the previous stack frame and implements function return.
. Size Max,.-Max
. Ident "GCC: (GNU) 3.4.1"
The above is a bit messy. Please analyze the code and examples of using the stack to implement the function call process. You can include more than four parameters when using the stack for parameter passing, and compile them to see how they are implemented.
Are you aware of the charm of stack? How perfect is the process call control implemented by stack. Haha.
After reading this knowledge point, it includes how the C language implements function calling and the specific implementation of parameter passing.
Then, do you naturally accept some concepts you encounter when learning C language. Such as local variables, life cycle of variables, and scope of variables. Now you know what a local variable is. The variable space is dynamically allocated and extinct with the execution of the program. The global variables are the specific memory areas reserved during compilation and linking, and will not die out. If the program runs on the operating system, unless its real program ends. This knowledge will be detailed in the compiler section.
In fact, there are two ways to allocate space on the computer: one is to reserve a space, or initialize or not initialize it. This space can be used by the program even if it is allocated. 2. Use commands to allocate space on the stack during the running of the program.
When we learn new things, we always have some questions. This is a good phenomenon. If you have any questions, it means you want to understand what is going on. If you really understand it, you can use this new thing to solve similar problems. If you don't have any questions about new things, but blindly trust and remember new things, then I say you cannot use it to solve any problems, because it's just your wishful thinking, and that new things don't know you at all, I don't want to take care of you either. Hey. From the previous knowledge, we can see that if you think about a concept at the level you are talking about, don't get discouraged and give up your doubts and curiosity. If you have this heart, you will be able to give a perfect answer to your questions in the future. Of course, you will not understand it from the original perspective. Just like C language, if you elaborate on the concept from the perspective of C language, you can't understand it either, but it's quite natural from the perspective of assembly and CPU. So we know that learning is a gradual process, but the premise is that you are curious to understand this thing, and never want to give up.
Make a metaphor; learning is like digging a well on the ground. You cannot easily dig out Gan Quan at a time. However, you may need to make many efforts and eventually do not necessarily get Gan Quan, but at least this well will get closer and closer to Gan Quan. We should not give up the confidence below. Also, we are born to enjoy the learning process. You may not be able to think about your own learning and walking, you can search online for videos of children learning to walk. It is very hard to learn to walk because it has to fall down many times, but they are very happy during their learning process. It's just that we have grown up, and even this basic pleasure is lost. There are many reasons. When we grow up, we have to consider more things and pay too much attention to the outside world. In fact, we should really enjoy the learning process, rather than the final score. Enjoy the scientific, rigorous, beautiful, and clever way of learning. Ah, let's talk about it more ~
Well, the execution of C-language functions is related to the stack.