0

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).

Yilei
  • 667
  • 5
  • 15

2 Answers2

3

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
lapk
  • 3,838
  • 1
  • 23
  • 28
1

Because you push those objects on stack and stack usually grows downwards. So, this is not guaranteed behavior, and is platform-specific. This is unrelated to how parameters are passed to functions though.

Community
  • 1
  • 1
  • Other than "not guaranteed", I'm not sure how any of this answers the question... – Oliver Charlesworth Dec 13 '11 at 21:36
  • @OliCharlesworth: Huh? You push A on stack first and B seconds. Because stack grows downwards, B's address is lower. If you push something else on stack after that, its address will be even lower. Until you run out of stack. And this is unrelated to how parameters to functions are passed. How is that not clear? –  Dec 13 '11 at 21:42
  • because cdecl pushes parameters from right-to-left, so `b` should get pushed first. I'm not sure what you mean by "this is unrelated to params are passed"; that's exactly what this is about, surely? – Oliver Charlesworth Dec 13 '11 at 21:43
  • @OliCharlesworth: I see what you saying. For some reason I answered why address of `a` and `b` are like that, and not their copies. Let me edit a bit –  Dec 13 '11 at 21:48
  • @VladLazarenko: `B` should be pushed first, then `A` – Yilei Dec 13 '11 at 22:49
  • @YileiYang: Sorry, I am still thinking how to put everything together nicely, but basically, when memory on stack is allocated, when stuff is pushed to stack, when constructor is called and how addresses are passed trough registers are not tight together, depend on calling conventions etc. For example, gcc decides to allocate stack at once for 4 objects (with -O0) or just 2 (-O3), having `foo` declared as extern, then is calls constructors in appropriate order and passes addresses trough registers. In this case they are pushed as 'a, b, a, b', so B address ends-up being lower. –  Dec 13 '11 at 23:16