DEV Community

Cover image for Some basic understanding of function calls
E_Chronosands::
E_Chronosands::

Posted on

Some basic understanding of function calls

Functions are a very important concept in program design. They can encapsulate relatively independent logic into a simple call, increasing the reusability of the logic. From a usage perspective, functions emphasize input and output; they accept parameters, perform a specific process, and then output the result.

A function is a type of subprogram, and it is also a type of callable unit. A programmer can write a function in source code that is compiled to machine code that implements similar semantics

Function calls are primarily accomplished using a stack data structure. In summary, It saves the current execution context by pushing onto the stack, then jumps to another piece of code to execute, and then pops the stack back to the previous context after execution is complete. And the structure used to save the current state is usually called a stack frame.

The structure used to save the current state is usually called a stack frame. From the perspective of high-level programming languages, it can be compared to a structure. However, in the underlying implementation using assembly language, it may simply use registers to record the top and bottom of the stack.

The CPU has no concept of functions, methods, classes, objects. The CPU only recognizes: instruction addresses, registers, memory, jumps, When you call an add function in a main function, for example:

int add(int a, int b) {
  // xxxx
}

int main() {
  add(1, 4);
  return 0;
}
Enter fullscreen mode Exit fullscreen mode

The underlying assembly might look like this (x86–64):

"add(int, int)":
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        mov     edx, DWORD PTR [rbp-4]
        mov     eax, DWORD PTR [rbp-8]
        add     eax, edx
        pop     rbp
        ret
"main":
        push    rbp
        mov     rbp, rsp
        mov     esi, 5
        mov     edi, 1
        call    "add(int, int)"
        mov     eax, 0
        pop     rbp
        ret
Enter fullscreen mode Exit fullscreen mode

The two crucial statements at the beginning of each function: push rbp and mov rbp, rsp, are used to create stack frames. (The purpose of push rbp is to restore the rbp register to its value before the call ends)

rbp and rsp are two commonly used register names.
rbp -> bottom of the current function stack
rsp -> top of the current stack

The call add(int, int) statement then performs two operations: saves the return address (next instruction address) and jumps to the new function to run:

And then it may also save some local variables and other information:

Next, some operations are performed using registers, and the results are saved to the eax register (this register is usually used to store the return value). Then, pop rbp restores the rbp register to its state before this function was called.

Finally, the ret statement corresponds to the call statement, and it also does two things: its function is to retrieve the return address from the top of the stack and jump back to main function. This concludes the call to the add function. Then execution will continue from the line mov eax, 0 in the main function.

Of course, please also note that the so-called popping of a stack frame may not actually clear old data for performance reasons; it may only change the state of registers.

Furthermore, with the advancement of modern compilers, many unnecessary steps are omitted, so the actual implementation may be even more streamlined and difficult to understand.

A more technical description of this process is that it involves temporarily transferring enforcement power and then restoring it.

Top comments (0)