Usually, when you write C programs for microcontrollers, you use functions that can be called any time in the main program or another function. The compiler compiles these functions as subroutines. These functions can be called from the main function or other functions – this is called nesting (nested subroutines).
If you see the compiled program’s listings, you will see that subroutines are called by using the call or rcall keyword. The argument of this instruction is a subroutine address that will be executed on the next processor cycle. Call instruction also writes return address from function to the stack to continue the program after returning from the function.
The next instruction begins execution at the start of the subroutine. Returning from the function is done after the ret instruction, which also restores the program counter’s address at the hardware level. The stack is also used to store all arguments of function – it usually depends on the compiler hos it is done.
But in general compiler generates code which:
- Push all arguments to stack;
- Call the function;
- Allocate storage for all local variables in the stack;
- Perform the function;
- Deallocate local variables from the stack;
- Return from function;
- Deallocate the space used by arguments.
All these operations may depend on the compiler and platform used. For instance, the AVR-GCC compiler uses registers for arguments and return values, and if there are too many (over 9) of them, others are passed to the stack. Read the FAQ. But it is recommended not to use too many parameters in functions if you want to use code efficiently.