In short: parameters to func
are "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
, _stdcall
etc 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: main
has enough stack space reserved to create temporaries a
and b
; b
is created first at lower address on stack of main
, which is fine, since the order of calling functions (copy-constructors A(const A&)
and B(const B&)
, in our case) within function call func
is explicitly undefined; addresses of temporaries a
and b
are stored on registers (and there is enough "home space" for "spilling" by func
reserved already); func
is called; func
can "spill" registers into "home space"; if it "spills" them, then address of b
will be "spilled" into stack at higher stack address than stack address into which the address of a
will 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 funcI
with int
parameters is to illustrate important points without mess of calling copy-constructors. Version of function with two parameters is, essentially, "sliced" version of posted funcC
- it's "sliced" to code that deals with RCX
and RDX
registers (to last ones). Also, note that RSP
contains stack pointer and inside funcI
and funcC
is less than RSP
inside main
by 8
(this explains offset when retrieving funcI
and funcC
last two parameters that were pushed on stack) :
class A
{
public:
int mData;
A()
{ // mov qword ptr [rsp+8],rcx ; "spilling" rcx
} // mov rax,qword ptr [rsp+8] ; RAX (return value) = pointer to the object;
A( const A& )
{// mov qword ptr [rsp+10h],rdx ;"spilling" RDX and RCX
// mov qword ptr [rsp+8],rcx
}// mov rax,qword ptr [rsp+8] ; notice, RAX has value of RCX
};
class B
{
public:
int mData;
B()
{ // mov qword ptr [rsp+8],rcx ; "spilling" rcx
} // mov rax,qword ptr [rsp+8] ; RAX (return value) = pointer to the object
B( const B& )
{// mov qword ptr [rsp+10h],rdx ;"spilling" RDX and RCX
// mov qword ptr [rsp+8],rcx
}// mov rax,qword ptr [rsp+8] ; notice, RAX has value of RCX
};
void __cdecl funcI( int a, int b, int c, int d, int e, int g )
{ // mov dword ptr [rsp+20h],r9d ; "spilling" registers, notice right-to-left order
// mov dword ptr [rsp+18h],r8d
// mov dword ptr [rsp+10h],edx
// mov dword ptr [rsp+8],ecx
a = 1; // mov dword ptr [rsp+8],1 ; accessing first 4 "spilled" function parameters
b = 2; // mov dword ptr [rsp+10h],2 ; notice, first function parameter has lower stack address than second parameter etc
c = 3; // mov dword ptr [rsp+18h],3 ; so parameters pushed right-to-left by callee's "spilling"
d = 4; // mov dword ptr [rsp+20h],4 ;
e = 5; // mov dword ptr [rsp+28h],5 ; here, accessing 2 parameters pushed on stack explicitly by caller
g = 6; // mov dword ptr [rsp+30h],6 ; they were pushed right-to-left
// also notice the offset of 8 in RSP ("g" is accessed through [rsp+30h] while it was put into [rsp+28h] in main
} // ret
void __cdecl funcC( A a1, A a2, A a3, A a4, A a5, B b1 )
{// mov qword ptr [rsp+20h],r9 ; "spilling"
// mov qword ptr [rsp+18h],r8
// mov qword ptr [rsp+10h],rdx
// mov qword ptr [rsp+8],rcx
a1.mData = 1;
// mov rax,qword ptr [rsp+8] ; same right-to-left order: a1 itself has lower stack address 'rsp+8' than a2 'rsp+18h'
// mov dword ptr [rax],1 ; HOWEVER, stack address value 'rsp+88h' stored in a1 is HIGHER than stack address value 'rsp+78h' stored in a2!!!!
a2.mData = 2;
// mov dword ptr [rax],2
// mov rax,qword ptr [rsp+18h]
a3.mData = 3;
// mov rax,qword ptr [rsp+18h]
// mov dword ptr [rax],3
a4.mData = 4;
// mov rax,qword ptr [rsp+20h]
// mov dword ptr [rax],4
a5.mData = 5;
// mov rax,qword ptr [rsp+28h]
// mov dword ptr [rax],5
b1.mData = 6;
// mov rax,qword ptr [rsp+30h]
// mov dword ptr [rax],6
} // ret
int main( void )
{
// sub rsp,0C8h ; reserving stack for `main`
A a;
// lea rcx,[rsp+30h] ; putting into RCX stack address of local 'a'
// call A::A()
B b;
// lea rcx,[rsp+34h] ; putting into RCX stack address of local 'b'
// call B::B()
funcI( 1, 2, 3, 4, 5, 6 );
// mov dword ptr [rsp+28h],6 ; passing parameters to `funcI`
// mov dword ptr [rsp+20h],5 ; last 2 on stack, first 4 on registers
// mov r9d,4 ; notice the right-to-left order:
// mov r8d,3 ; "6" is put on stack at higher address than "5" etc.
// mov edx,2 ; notice also that "6" is put into [rsp+28h]: during call to `funcI` RSP will be less by 8
// mov ecx,1 ; and inside `funcI` parameter will be accessed accordingly through [rsp+30h]
// call funcI
funcC( a, a, a, a, a, b );
// lea rax,[rsp+38h] ; some preparations: putting stack addresses of temporaries into stack variables
// mov qword ptr [rsp+40h],rax ; there are few indirections in debug mode, we can ignore them, noticing the addresses
// lea rax,[rsp+48h]
// mov qword ptr [rsp+50h],rax
// lea rax,[rsp+58h]
// mov qword ptr [rsp+60h],rax
// lea rax,[rsp+68h]
// mov qword ptr [rsp+70h],rax
// lea rax,[rsp+78h]
// mov qword ptr [rsp+80h],rax
// lea rax,[rsp+88h]
// mov qword ptr [rsp+90h],rax
// lea rdx,[rsp+34h] ; putting into RDX stack address of local 'b'
// mov rcx,qword ptr [rsp+40h] ; putting into RCX stack address of temporary - this is right-most temporary of type `B` in `funcC`:
// call B::B(const B&) ; [rsp+40h] = (rsp+38h) which is stack address of temporary `b`
// mov qword ptr [rsp+98h],rax ; putting on stack value of RAX: [rsp+98h] now contains (rsp+38h)
// lea rdx,[rsp+30h]
// mov rcx,qword ptr [rsp+50h]
// call A::A(const B&)
// mov qword ptr [rsp+0A0h],rax
// lea rdx,[rsp+30h]
// mov rcx,qword ptr [rsp+60h]
// call A::A(const A&)
// mov qword ptr [rsp+0A8h],rax
// lea rdx,[rsp+30h]
// mov rcx,qword ptr [rsp+70h]
// call A::A(const A&)
// mov qword ptr [rsp+0B0h],rax
// lea rdx,[rsp+30h]
// mov rcx,qword ptr [rsp+80h]
// call A::A(const A&)
// mov qword ptr [rsp+0B8h],rax
// lea rdx,[rsp+30h]
// mov rcx,qword ptr [rsp+90h]
// call A::A(const A&) ; notice, RAX is not copied onto stack, it's preserved (see below)
// mov rcx,qword ptr [rsp+98h] ; passing parameters (ignoring indirections): putting stack address of right-most temporary `b` onto stack
// mov qword ptr [rsp+28h],rcx ; notice stack address 'rsp+28h' where stack address of 'b' 'rsp+38h' is put
// mov rcx,qword ptr [rsp+0A0h] ; same for right-most temporary of type 'A' (indirection again)
// mov qword ptr [rsp+20h],rcx ; stack address 'rsp+20h' where stack address 'rsp+48h' of right-most `a` is put
// mov rcx,qword ptr [rsp+0A8h] ; NOW: this is RIGHT-TO-LEFT order - take a closer look:
// mov r9,rcx ; rsp+28h > rsp+20h but value [rsp+28h] = rsp+38h < rsp+48h = [rsp+20h]
// mov rcx,qword ptr [rsp+0B0h] ; parameters are pushed right-to-left, parameters' values are not ordered this way (and, really, can be anything)
// mov r8,rcx ; other 4 parameters are put on registers
// mov rcx,qword ptr [rsp+0B8h]
// mov rdx,rcx
// mov rcx,rax ; notice, preserved RAX is put into RCX - it has stack address of first temporary of type 'A'
// call funcC
return ( 0 ); // xor eax,eax ; return value is 0
}
// add rsp,0C8h ; restoring stack
// ret