22

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:

  1. Variables, and extended complex structures
    We can describe complex states based on variables.
  2. 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:

  1. variable
    Local variables, including function parameters, are all pushed into the stack .
  2. function
    The function itself is compiled separately into a machine instruction
    The function call is compiled into the call 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:

  1. When the function is executed, the local variable should be invalid when it returns
  2. 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

  1. 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.
  2. For C language functions, the compiler will compile into a separate piece of machine instructions
    Calling this function is to execute the call instruction, which means to jump to the execution of this machine instruction next.

doujiang24
209 声望1k 粉丝

Core developer of OpenResty.