When can return be omitted during recursion in C language: Interpreting the essence of return in C language functions through inline assembly
The story goes like this. The blogger uses recursion when writing a simple business in C, and forgets to write return due to carelessness. The returned results are still correct. After 30 minutes of disassembly and debugging, I have proved my conjecture and I am sharing it in my blog. It is also a deep understanding of the C language compilation principles. Introduction: First of all, I want to use a question as an example to illustrate the problem.
Example 1: # include
/** Function: Implement bitwise addition using recursion */int Add_Recursion (int a, int B) {int carry_num = 0, add_num = 0; if (B = 0) {return a;} else {add_num = a ^ B; carry_num = (a & B) <1; Add_Recursion (add_num, carry_num);} int main () {int num = Add_Recursion (1, 1); printf ("% d \ n", num); getchar ();}
The problem is, how many values are printed when the above program is executed? Everyone may think that this is very mentally retarded, even if the pen questions of a small company are not in the elegant classroom.
----------- Execution result of Example 1 in Figure 1 ------- the answer is 2. There is no doubt that it is just a simple recursion.
But if I change the question
Example 2: # include
Int changestack () {return 3;}/** function: Implement bitwise addition using recursion */int Add_Recursion (int a, int B) {int carry_num = 0, add_num = 0; if (B = 0) {return a;} else {add_num = a ^ B; carry_num = (a & B) <1; Add_Recursion (add_num, carry_num); changestack () ;}} int main () {int num = Add_Recursion (1, 1); printf ("% d \ n", num ); getchar ();}
Let's take a look at the above program. What is the execution result?
Many of my friends may have noticed that they are tricky.
Some may be confused. This program only adds an insignificant function call after the Recursive Implementation of the function. Why does it affect the result returned by the function.
In fact, the result printed by printf is incorrect. The running result is 3.
VcHLtO3O87XEt7W72Na1oaMNCjxwcmUgY2xhc3M9 "brush: java;">
else { add_num = a^b; carry_num = (a&b)<<1; return Add_Recursion(add_num, carry_num); changestack(); }
If you correct the above Code, there will be no problems. (Of course, there will be no error. With return, changestack after return will not have any chance to execute it)
Now let's analyze the nature of errors step by step.
------- Recursive Analysis of function 3 ---------
After analyzing the running process of the above Code, we first call Add_Recursion () in the main function. The intention is to calculate the value of 1 + 1 and pass the return value of the function to printf for printing.
When the Add_Recursion function (add) is called recursively to calculate 1 + 1, the first two recursive calls do not meet the recursive exit conditions (carry increment carry_num is 0 ), will jump into the else branch for recursive calling. Until the third recursive call, because carry_num is 0, the cumulative result is returned.
The problem is that only the third add recursive call performs return. The first and second times do not return when the function returns, but call changestack () after returning the sub-hierarchy recursion () after the function is called, The system returns its own function level. When the first layer of recursive call is returned to main, add_recursion does not return, but directly returns the main function after changestack is executed. At this time, when the printf of the main function parses the return value, in fact, the returned value of changestack is parsed incorrectly. Therefore, the error "1 + 1 = 3" occurs. In this case, we can analyze the causes of all these errors:
When function execution ends, the returned value is pushed to the stack.(Theoretically,
In fact, the compiler will optimize and transition the returned value to the eax register, VC is used to temporarily save eax ). When the VC compiler parses the return value (integer) of a function, the eax value is directly read as the return value.
-------- Figure 4. disassembly analysis: the VC compiler processes return. According to the disassembly analysis, the result of the VC Compiler's return 3 Assembly in changestack () is mov eax, 3. In fact, it is to assign the return value to eax, which is used by the call function of this function through the eax register.
In the main function, we can see the specific process of assigning the value of changestack () to num, that is, returning the value of eax to the memory address of num.
---------- Details of the function return value in Figure 5-"pop-up stack -----------
In this way, everything is explained.
------- Figure 6. Example 1: Why does it happen to be a correct recursive analysis -----
Although the results in the first question are correct, when printf reads the Add_Recursion return value, it does not read the result of the first recursive call, but the result of the third recursive call of return B (the third recursive return, stored in the eax register ). In the subsequent recursive return, it happens that eax is not changed. Therefore, using recursion in this way (although return is not needed) can obtain the correct result.
In fact, we can use an inline assembly code to verify whether our conjecture is correct. After recursive calling, we add an assembly code to inline assembly to change the value of eax.
----------- Figure 7 interpret the return Nature of C language with inline assembly ----------
We added an assembly code behind the recursive function Add_Recursion to change the value of eax at the end of the function. We can see that in the main function, the return value of the function is mistaken for the 3. We set in the assembly language to print out the 1 + 1 = 3 paradox.
In fact, the C compiler will give a warning when the program in the compilation example is compiled.
Warning C4715: "Add_Recursion": not all control paths return values.
For a function with a return value, not all branches return values. If you compile the program in the blog on a more rigorous C ++ compiler, an error is returned.
This is just a very simple case. We may be lucky to implement the function, but if we are not sure whether we can get the final result in tree-like or even graph-like recursion of complex situations, be sure to return the return value for each case to avoid unexpected program errors. The flexibility of C language should benefit us, rather than providing unstable factors for our programs.