C++ (nested) function call instructions - register

2020-06-05 05:55发布

问题:

In C++ FAQ:

Assuming a typical C++ implementation that has registers and a stack, the registers and parameters get written to the stack just before the call to g(), then the parameters get read from the stack inside g() and read again to restore the registers while g() returns to f().

regarding the nested function call

void f()
{
  int x = /*...*/;
  int y = /*...*/;
  int z = /*...*/;
  ...code that uses x, y and z...
  g(x, y, z);
  ...more code that uses x, y and z...
}

1/ Are all implementation of C++ with registers and stack? Does it mean: implementation dependent on compiler/processor/computer architecture?

2/ What is sequence of instructions (without assembly language, just the big picture) when i call f() ? I have read diverging things on this topic, and also I don't remember that registers where mentionned, but only stack.

3/ what are additional specificities/points to underline when you deal with nested functions?

thanks

回答1:

For number 2 this depends on many things including the compiler and the platform. Basically the different ways of passing and returning arguments to functions are called calling conventions. the article Calling conventions on the x86 platform goes into some detail on sequence of operations and you can see how ugly and complicated it gets with just this small combination of platforms and compilers which is most likely why you have heard all sorts of different scenarios, The gen on function calling conventions. covers a wider set of scenarios including 64 bit platforms but is harder to read. It gets even more complicated because gcc may not actually push and pop the stack but directly manipulate the stack pointer, we can see an example of this, albeit in assembly here. It is hard to generalize about calling conventions, if the number of arguments is small enough many calling conventions can avoid using the stack at all and will use registers exclusively.

As to number 3, nested functions does not change anything, it will just repeat the procedure over again for the next function call.

As to number 1 As Sean pointed out .Net compiles to byte code with performs all it's operations on the stack. The Wikipedia page on Common Intermediate Language has a good example.

The x86-64 ABI document is another great document if you want to understand how one specific calling convention works in detail. Figure 3.5 and 3.6 are neat since they give a nice example of a function with many parameters and how each parameter is passed using a combination of general purpose registers, floating point registers and the stack. This sort of nice diagram is a rare find when looking at documents that cover calling conventions.



回答2:

1. Although register/stack implementations are the most common underlying implementations of a C++ compiler there's nothing to stop you using a different architecture. For example, you could write a compiler to generate Java bytecode or .NET byte code, in which case you'd have a stack based C++ compiler.

2. When you call f() the typical approach is:

  • Push the return address on the stack and jump to f()

  • In f():

  • Allocate space for the locals x,y and z. This is normally done on the stack. Take a look at this article on call stacks.

  • When you get to g(x,y,z) the compiler will generate code to push the values onto the stack by accessing their values in the stack frame of f(). Note that C/C++ pushes parameters from right to left.

  • When you get to the end of f() the compiler inserts a return instructions. The top of the stack has the address to return to (it was pushed prior to the call to f() )

3. There's nothing special about nested functions as everything follows the same basic template:

  • To call a function - push parameters and call the function.
  • Within the function - allocate space for local variables within a stack

Now this is the general approach. Compilers will introduce their own optimizations to improve performace. For example a compiler may choose to store the first 2 parameters in registers (for example).

NOTE: Although parameter passing by the stack is by far the most common approach there are others. Take a look at this article on register windows if you're interested in finding out more.