Sep 13, 2015

Pass-by-value vs. pass-by-const-reference - Part 2

This is the continuation of the comparison of pass-by-value and pass-by-const-reference. In this post I will do comparison between user-defined classes.

As in my previous post, I'm using Visual Studio 2010 Express in Debug mode and WinDbg to view the disassembly.

First a short catch up what I've done in Part 1. We have added two integers in a function called add.

int main()
{
   int a = 3;
   int b = 7;
   int c = add(a, b);
   return 0;
}

When using the pass-by-value version of add, copies of 3 and 7 were pushed on the stack.

When using the pass-by-const-reference, the memory addresses were pushed on the stack.

This was clearly shown in the memory view of WinDbg.

I'm now going to evolve the add example above a little bit.

Let's say we have user-defined class according to below.

class MyClass
{
public:
   int a_, b_;
};

Further, let's modify the main function.

int main()
{
   MyClass myClass = {3, 7};
   int c = add(myClass);

   return 0;
}

I'm using the same idea as in Part 1. But my add function takes a MyClass type instead of two integers.

First up is the pass-by-value example. Below is the C++ code and the disassembly with the stack.

int add(MyClass myClass)
{
  return myClass.a_+myClass.b_;
}


WinDbg - disassembly of main (about to execute the call instruction) and its stack
Above, we can see that the result is pretty much as the pass-by-value example for the integers in the Part 1. We first initialize the myClass.a_ to 3, the initializing myClass.b_ to 7.
First is 7 (myClass.b_) pushed on the stack, and then is 3 (myClass.a_) pushed on the stack.

The blue and purple frames is the result of the copying, i.e. the push instructions.

Now let's continue with the disassembly of the add function.



WinDbg - disassembly of add function (about to execute the move eax instruction) and its stack
Above, follow the blue and purple lines, and you will see that the add function use the pushed values (i.e. the copies of the original values) to do the calculation.

Finally, it is time to investigate what is going on the the pass-by-const-reference case for our user-defined class.

int add(const MyClass &myClass)
{
   return myClass.a_+myClass.b_;
}


WinDbg - Disassembly of main function (about to execute the call instruction) and its stack
Above, as in the pass-by-value case, the red and green frame sets the variables myClass.a_=3 and myClass.b_=7.

The lea instruction stores the memory address of the first member in myClass, i.e 3 (myClass.a_) in eax, which is then pushed on the stack (blue frame).

Then we are ready to execute the add function, i.e. the call instruction.


WinDbg - Disassembly of add function (about to execute the move eax instruction) and its stack
The move eax, dword ptr [ebp+8] instruction, sets eax to the address of myClass.a_. Next instruction sets eax to the contents of the address which eax points to, i.e. value 3.

Same procedure is done for myClass.b_. The move ecx dword ptr [ebp+8] instruction, sets ecx to the address of myClass.a_. Next instruction add eax (3) to the contents of the address which ecx+4 points to, i.e value 7.

We have finally investigate what is happening for both integers and user-defined classes when using pass-by-value and pass-by-const-reference. When the user-defined classes grows in terms of members, the copying can be very expensive.

You are welcome to leave comments, complaints or questions!