What happens when a function is called

Location
  1. Courses

    /

  2. Complete C++ Course

    /

  3. The basics

    /

  4. What happens when a function is called

The instruction pointer

Instructions, like variables, are stored in the memory of the computer. The processor stores the address of the currently executed instruction (In the instruction pointer) and changes it to the address of the next one once the instruction is finished. A function is basically a set of instructions defined somewhere in the memory which ends with an instruction which indicates the processor to continue back the execution of the program at the address after the instruction that called the current function.

So when we call a function, what happens is that we tell the processor to set the instruction pointer to the address of the function. Once finished, it is set back to where it left off, but after the instruction that called the function.

However, this is a bit more complex than that. Before we see why, let us have a look at what is a register of a processor and what is a memory stack.

Registers

Processors have a small amount of registers (The number depends on the processor, but it may be something like 16). A register is an extremely fast memory space (The fastest of the computer) which hold very few bytes. A 64bit processor has registers made of 8 octets (64 bits). Most of the instructions a processor can execute interact with at least one of its registers. For example, let us say we want to multiply two variables (Which are stored in the memory of the computer (The RAM)) and store the result into an other variable. The first step is to load their value into registers, the second one is to execute the multiplication instruction using the two registers and then copy the result (Which is stored in a register) back to the computer memory, at the address of the third variable.

Memory stacks

Each program has what is called a stack. It is a location in the memory where we can save the value of a register in order to retrieve it later. By pushing the value of a register to the stack, the stack size increases and an element (the value of the register) is added to it. We can pop (retrieve) the last value added to the stack back into a register. The size of the stack is then reduced and the last element is removed from it. It is possible to read the value of all the elements in the stack, but we can only pop (Take off) the last one. A memory stack is often compared to a stack of plates. We can look at all the plates, but we can only take off the one on the top (Well, if we only use one hand). Similarly, we can only add plates on the top.

Why do we need to use memory stacks? We need them because the amount of data the registers can hold is very limited, so it is sometime necessary to temporarily save the value of the currently unused registers to the memory (RAM) to free them, while still being able to recover their old value later.

Calling a function

When we call a function, the address currently pointed by the instruction pointer of the processor (The instruction pointer is actually a register) is pushed on the stack, so we can go back to that address later. Other registers may also be pushed to the stack so the value they hold can be retrieve after the function is executed. After that, the arguments (If there are) of the function are pushed to the stack and/or loaded into registers (Usually the first few arguments are loaded into registers and if there are more, they are pushed to the stack). The variables that are local to the function (The variables for which the life span is the function)(If there are) are also pushed to the stack and/or loaded into registers. The processor then sets its instruction pointer to the start of the code of the function and execute it. When the end of the function is reached (When the processor reaches the instruction that tells it to return from the function), the processor takes off the variables added to the stack and goes back to where the function has been called, skipping the previous function call instruction.

As you can see, if we keep calling functions without returning, the stack would not stop growing. The amount of memory the stack can use is limited. If it grows too big, it will overflow and access a memory zone that does not belong to the program. That is called stack overflow. If that happens, the operating system will kill the process (The program will crash). That kind of problem is most likely to happen when using recursive functions. We will learn about that kind of function later. For now, just know that a relatively huge amount of functions calls need to be made without returning before a stack overflow happen. Note that for functions that return nothing, the return is implicit. Such functions return when the end of their code is reached.

You should now understand why calling functions causes overhead. That is the reason why, performance-wise, calling a function is worth only when the function contains a great amount of instructions and is called in many places in the code (So it reduces the size of the executable file because the code of the function is not written many times). However, on a code readability point of view, functions are almost always useful. A function named printStringToConsole probably tells you more about what it does then a set of line of code.

In general, we should not worry too much about function overhead. Compilers do a lot of optimizations in the background. For example, they may replace some of our function calls by the instructions inside them, if they consider it is not worth calling them. We will also see, soon, that it is possible to recommend the compiler to do so.