Last article "What does the CPU provide?" In , we learned about the physical level of the CPU and what it provides us.
In this article, we introduce how the high-level language "C language" runs on a physical CPU.
What does C language provide
As a high-level language, C language provides programmers with a more friendly way of expression. In my opinion, it mainly provides the following abstract capabilities:
- Variables, and extended complex structures
We can describe complex states based on variables. - function
We can split complex behavior logic into different functions based on functions to simplify complex logic. And, we can reuse functions with the same purpose, and a large number of basic libraries in the real world simplify the coding work of programmers.
Sample code
Building a good sample code can help us understand.
In the following example, we can see that the variable and function are used.
#include "stdio.h"
int add (int a, int b) {
return a + b;
}
int main () {
int a = 1;
int b = 2;
int c = add(a, b);
printf("a + b = %d\n", c);
return 0;
}
Compile and execute
3
surprisingly, we got the expected 06087bd814a51b.
$ gcc -O0 -g3 -Wall -o simple simple.c
$ ./simple
a + b = 3
Assembly code
We still use objdump
to see what code the compiler generates:
- variable
Local variables, including function parameters, are all pushed into the stack . - function
The function itself is compiled separately into a machine instruction
The function call is compiled into thecall
instruction, and the parameter is the first instruction address of the machine instruction corresponding to the function.
$ objdump -M intel -j .text -d simple
# 截取其中最重要的部分
000000000040052d <add>:
40052d: 55 push rbp
40052e: 48 89 e5 mov rbp,rsp
400531: 89 7d fc mov DWORD PTR [rbp-0x4],edi
400534: 89 75 f8 mov DWORD PTR [rbp-0x8],esi
400537: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8]
40053a: 8b 55 fc mov edx,DWORD PTR [rbp-0x4]
40053d: 01 d0 add eax,edx
40053f: 5d pop rbp
400540: c3 ret
0000000000400541 <main>:
400541: 55 push rbp
400542: 48 89 e5 mov rbp,rsp
400545: 48 83 ec 10 sub rsp,0x10
400549: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1
400550: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2
400557: 8b 55 f8 mov edx,DWORD PTR [rbp-0x8]
40055a: 8b 45 fc mov eax,DWORD PTR [rbp-0x4]
40055d: 89 d6 mov esi,edx
40055f: 89 c7 mov edi,eax
400561: e8 c7 ff ff ff call 40052d <add>
400566: 89 45 f4 mov DWORD PTR [rbp-0xc],eax
400569: 8b 45 f4 mov eax,DWORD PTR [rbp-0xc]
40056c: 89 c6 mov esi,eax
40056e: bf 20 06 40 00 mov edi,0x400620
400573: b8 00 00 00 00 mov eax,0x0
400578: e8 93 fe ff ff call 400410 <printf@plt>
40057d: b8 00 00 00 00 mov eax,0x0
400582: c9 leave
400583: c3 ret
400584: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
40058b: 00 00 00
40058e: 66 90 xchg ax,ax
Why are the local variables in the function put into the stack space?
This happens to be associated with the scope of local variables:
- When the function is executed, the local variable should be invalid when it returns
- When the function returns, it happens to restore the stack height to the previous caller function.
In this case, only the stack height needs to be restored, which means that all temporary variables of the called function are all invalidated.
Will the local variables in the function be put into the stack space?
The answer is not necessarily.
Above we are -O0
. Next, let's look at the machine code generated by -O1
At this time, the local variable is directly placed in the register and does not need to be written to the stack space.
However, at this time main
no longer calls the add
function, because it has been optimized inline by gcc.
Well, it is not easy to construct a suitable use case.
000000000040052d <add>:
40052d: 8d 04 37 lea eax,[rdi+rsi*1]
400530: c3 ret
0000000000400531 <main>:
400531: 48 83 ec 08 sub rsp,0x8
400535: be 03 00 00 00 mov esi,0x3
40053a: bf f0 05 40 00 mov edi,0x4005f0
40053f: b8 00 00 00 00 mov eax,0x0
400544: e8 c7 fe ff ff call 400410 <printf@plt>
400549: b8 00 00 00 00 mov eax,0x0
40054e: 48 83 c4 08 add rsp,0x8
400552: c3 ret
400553: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
40055a: 00 00 00
40055d: 0f 1f 00 nop DWORD PTR [rax]
Prohibit inline optimization
We use the following command to turn off gcc's inline optimization:
gcc -fno-inline -O1 -g3 -Wall -o simple simple.c
Looking at the assembly code again, the machine code at this time is in line with the ideal verification result.
000000000040052d <add>:
40052d: 8d 04 37 lea eax,[rdi+rsi*1]
400530: c3 ret
0000000000400531 <main>:
400531: 48 83 ec 08 sub rsp,0x8
400535: be 02 00 00 00 mov esi,0x2
40053a: bf 01 00 00 00 mov edi,0x1
40053f: e8 e9 ff ff ff call 40052d <add>
400544: 89 c6 mov esi,eax
400546: bf f0 05 40 00 mov edi,0x4005f0
40054b: b8 00 00 00 00 mov eax,0x0
400550: e8 bb fe ff ff call 400410 <printf@plt>
400555: b8 00 00 00 00 mov eax,0x0
40055a: 48 83 c4 08 add rsp,0x8
40055e: c3 ret
40055f: 90 nop
to sum up
- For C language variables, the compiler will allocate a memory space for storage
It is an ideal way to map the local variables in the function into the stack space. However, in the optimized mode of compilation, registers are used as much as possible for storage, and the stack space is used only when the registers are not enough.
Global variables are stored in corresponding memory segments, which can be discussed later. - For C language functions, the compiler will compile into a separate piece of machine instructions
Calling this function is to execute thecall
instruction, which means to jump to the execution of this machine instruction next.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。