I did an experiment:
class A {
public:
A() {}
A(const A& a) {
printf("A - %p\n", this);
}
};
class B {
public:
B() {}
B(const B& b) {
printf("B - %p\n", this);
}
};
void func(A a, B b) {}
int main() {
A a;
B b;
func(a, b);
return 0;
}
The output is:
B - 0x7fff636e2c48
A - 0x7fff636e2c50
Since the parameters are pushed from right to left, why B’s address is lower than A’s? Confused. (Stack starts from the higher address).
In short: parameters to
funcare “pushed” on stack correctly right-to-left; you are printing not the stack addresses of these parameters, but values of these parameters which also happen to be SOME addresses on stack.In slightly more detail…
First of all, you are on x64 machine. You should forget about
_cdecl,_stdcalletc calling conventions. There is only one calling convention (simplified version): first four function parameters (listed left-to-right in function call) will be passed on registers, the rest – on stack. Now, that said, caller is required to allocate enough “home space” on its own stack, so callee can “spill” there the parameters passed on registers. So, in principle, those first four parameters might be found on stack as well, if callee decides to use stack via “home space”.Second, if parameters passed on registers are “spilled” by callee, they are still “pushed” on stack right-to-left: callee will “spill” registers into “home space” using right-to-left order, meaning the first function parameter will end up having lower address on stack in the “home space” region than second parameter. So, in this respect parameters are always “pushed” on stack right-to-left.
Third, your output has nothing to do with how parameters are passed to
func, but with where temporaries are created. This is what happens:mainhas enough stack space reserved to create temporariesaandb;bis created first at lower address on stack ofmain, which is fine, since the order of calling functions (copy-constructorsA(const A&)andB(const B&), in our case) within function callfuncis explicitly undefined; addresses of temporariesaandbare stored on registers (and there is enough “home space” for “spilling” byfuncreserved already);funcis called;funccan “spill” registers into “home space”; if it “spills” them, then address ofbwill be “spilled” into stack at higher stack address than stack address into which the address ofawill be “spilled” – this is right-to-left order of passing parameters.Here is some code and corresponding assembler. Note, I used modified code (more parameters and two functions) to show how parameters are passed. The function
funcIwithintparameters is to illustrate important points without mess of calling copy-constructors. Version of function with two parameters is, essentially, “sliced” version of postedfuncC– it’s “sliced” to code that deals withRCXandRDXregisters (to last ones). Also, note thatRSPcontains stack pointer and insidefuncIandfuncCis less thanRSPinsidemainby8(this explains offset when retrievingfuncIandfuncClast two parameters that were pushed on stack) :