Saturday, January 21, 2012

C++ class internals - part 1/2


class A {
   public:
       int a_;
       void setA( int a ) { a_ = a; }
       int getA( void ) { return a_; }
};

I think this is not a very difficult class to understand. Pretty straightforward. But, how does the C++ compiler handle this. If you watch closely, the 'class' as mentioned in all the C++ books is a concept. The concept is being presented to you. The concept says that 

 "a class is similar to a structure ... it has public, private, protected access specifiers .... there can be variables and functions in a class .. both of which can be either member or non-member types ... classes can be inherited ... blah blah blah ..."

So when a developer reads this, immediately he pictures a 2D-box ... which contains all the ( variables + functions ) .... which denote a class ... and another box to denote another class and a line connecting these 2 to denote inheritance ... Now ... the concept is declared sold ... because that's what was expected from the reader ... If you want to learn a bit more on how this concept is turned into a reality .. keep reading ...

Let' get started.

1. Class structure:


// class_test.c

#include <iostream>
using namespace std;

class A {
   public:
       int a_;
       void setA( int a ) { a_ = a; }
       int getA( void ) { return a_; }
};

int main() {
   A a;
   a.setA( 10 );
   cout << a.getA() << endl;

   return 0;
}

Output:
-------

10

ok ... this program outputs '10' which is no magic. Lets get a bit deeper. When we declare the following class, what exactly does the compiler do ?

class A {
   public:
       int a_;
       void setA( int a ) { a_ = a; }
       int getA( void ) { return a_; }
};

We've seen 'variables' within structures in C. When we say sizeof(struct some_struct ), we get the number of bytes occupied by the structure. But, what does 'functions' within structure mean ? Do functions add extra space to the structures ? Functions are normally outside structures .... right ?

Well here is the answer. In fact, functions are always outside structures ...  more simpler to say .. these functions are exactly how they're in C, except that they are placed inside the 'class' structure .. Remember there is nothing called a C++ function in assembly ... assembly supports only C style functions. In short, you can say that the C++ compiler re-interprets the above structure as follows:

class A {
   public:
       int a_;
};

void setA( int a ) { a_ = a; }
int getA( void ) { return a_; }

Phew .... this is more of a 'C' style code. Ah ... I'm feeling at home ......     Arggg .... wait a minute !!! There is something wrong here. The 'a_' is inside the structure ... and it doesn't sound right to place setA() / getA() outside ....  you know ... these functions can't access "a_" ... If you thought this way ... then 'C++' conceptualizers have convinced you to the core ...

To be convinced that this is not a problem ... we've to start with the first line in main():

2. Class declaration:
  
A a;
 
 When the compiler encounters this line .... it sees that a structure "A" with a single member "int a_" is declared as below:

class A {
   public:
       int a_;
};

So, it allocates 4 bytes in memory. Simple !   You might say "wait ... this line is not that simple .... I read that the object's constructor gets called here .... "  Yes ...we're coming to that next ... A constructor is a simple initialization function ... that gets called when the object 'a' is created.  "Wait ... I didn't write any initialization function ... so where did it come from ... ?" .... the compiler writes one for you automatically .... "Yes ... I keep hearing that a lot of times  ... anything new ? " ... we're going to do a simple test to check if this is true ... ;-)


[...]$ g++ -m32 class_test.c
[...]$ gdb ./a.out
(gdb) disassemble main
Dump of assembler code for function main:
...

...
0x00001cf8 <main+24>:    call   0x1e92 <__ZN1AC1Ev>
0x00001cfd <main+29>:    mov    -0x18(%ebp),%eax
0x00001d00 <main+32>:    mov    %eax,(%esp)
0x00001d03 <main+35>:    movl   $0xa,0x4(%esp)
0x00001d0b <main+43>:    call   0x1e8c <__ZN1A4setAEi>
0x00001d10 <main+48>:    mov    -0x18(%ebp),%eax
0x00001d13 <main+51>:    mov    %eax,(%esp)
0x00001d16 <main+54>:    call   0x1e86 <__ZN1A4getAEv>

...  
... 

Don't get scared by the above lines. We're just trying to open our program's binary in gdb, and looking into the assembly code of our main function. One thing to note is that there are 3 call instructions. All 3 are function calls. The names of the functions are cryptic ( which is a feature called c++ name mangling ). It can be converted to human-readable names using 'c++filt' ( or gc++filt if you're on a non-gnu platform ) program. So, here is the output after conversion.

0x00001cf8 <main+24>:    call   0x1e92 <A::A()>
0x00001cfd <main+29>:    mov    -0x18(%ebp),%eax
0x00001d00 <main+32>:    mov    %eax,(%esp)
0x00001d03 <main+35>:    movl   $0xa,0x4(%esp)
0x00001d0b <main+43>:    call   0x1e8c <A::setA(int)>
0x00001d10 <main+48>:    mov    -0x18(%ebp),%eax
0x00001d13 <main+51>:    mov    %eax,(%esp)
0x00001d16 <main+54>:    call   0x1e86 <A::getA()>


 And so there it is. The main() function in fact calls A::A(), which is A's default constructor generated by compiler. One thing to note is that setA() and getA() are called after the constructor. This makes sense, since the constructor should be called to construct the object. After the object is constructed, any function can be called on the object. If you're more curious on what is in A::A(), lets try to disassemble that too. Remember constructors are normal functions too. So, we'll disassemble it as we did main():

(gdb) disassemble __ZN1AC1Ev
Dump of assembler code for function __ZN1AC1Ev:
0x00001e5c <A::A()+00>:    push   %ebp
0x00001e5d <A::A()+01>:    mov    %esp,%ebp
0x00001e5f <A::A()+03>:    sub    $0x4,%esp
0x00001e62 <A::A()+06>:    call   0x1e67 <A::A()+11>
0x00001e67 <A::A()+11>:    pop    %eax
0x00001e68 <A::A()+12>:    mov    0x8(%ebp),%ecx
0x00001e6b <A::A()+15>:    mov    %ecx,-0x4(%ebp)
0x00001e6e <A::A()+18>:    mov    -0x4(%ebp),%ecx
0x00001e71 <A::A()+21>:    mov    0x1bd(%eax),%eax
0x00001e77 <A::A()+27>:    lea    (%eax),%eax
0x00001e79 <A::A()+29>:    add    $0x8,%eax
0x00001e7c <A::A()+32>:    mov    %eax,(%ecx)
0x00001e7e <A::A()+34>:    add    $0x4,%esp
0x00001e81 <A::A()+37>:    pop    %ebp
0x00001e82 <A::A()+38>:    ret 
End of assembler dump.

_ZN1AC1Ev is the mangled name for A::A(). So, we disassemble it here. What's in here is not important. The point is there is some auto-generated code for A::A(). Now let's modify our program a little bit. Let's add our own constructor. The modified version of the above program is below:

#include <iostream>
using namespace std;

class A {
   public:
       A() { a_ = 100; }
       int a_;
       void setA( int a ) { a_ = a; }
       int getA( void ) { return a_; }
};

int main() {
   A a;
   a.setA( 10 );
   cout << a.getA() << endl;

   return 0;
}

Now let's disassemble the constructor.


(gdb) disassemble A::A()
Dump of assembler code for function A::A():
0x00001e28 <A::A()+00>:    push   %ebp
0x00001e29 <A::A()+01>:    mov    %esp,%ebp
0x00001e2b <A::A()+03>:    sub    $0x4,%esp
0x00001e2e <A::A()+06>:    mov    0x8(%ebp),%eax
0x00001e31 <A::A()+09>:    mov    %eax,-0x4(%ebp)
0x00001e34 <A::A()+12>:    mov    -0x4(%ebp),%eax
0x00001e37 <A::A()+15>:    movl   $0x64,(%eax)
0x00001e3d <A::A()+21>:    add    $0x4,%esp
0x00001e40 <A::A()+24>:    pop    %ebp
0x00001e41 <A::A()+25>:    ret  
End of assembler dump. 

You can pretty much ignore most of the lines except for the line in bold. This line just sets the value of a_ to 0x64 which is 100. We'll cover more on how a_ is accessed in the next topic. But, the point to note here is that there is no default auto-generated constructor now. Compiler doesn't generate one, if you've declared your own constructor. We'll cover constructors in more detail in part 2 of this blog. The idea here is to just present a high level overview.

Ok ... now we're pretty much done with the declaration line. So, let's go to the next line.

3. C++ member function call:
   
 a.setA( 10 );

This calls the setA() function of object / instance 'a' with an argument of 10. We'll this is the C++ way of reading this line. Let's get to the internals. Before going into this I want to make one point clear. Assembly is the original language out there. I mean any other out-of-the-box alien language you invent has to be finally be converted to assembly for it to work. This is the case for C++, C, Python etc. As already mentioned C functions are more close to assembly, which gives us a small clue. C++ functions need to be converted into some form of C code before it gets to assembly. Why I'm telling this now is that the above line will be processed internally by the C++ compiler, as if it's a normal 'C' function.

Ok. Let me modify the above as follows:

 setA( 10 );
 
Cool ....... !   now, that is C code. I can hear complaints "This function needs to be called on the instance / object 'a' ... you know .... this call is kinda closely bound to 'a', and it can't just fly around like a free bird ... i mean it can't be free like a C function ...".  True, but the next step will clarify this. Let's modify the above line as below:

 setA( &a, 10 );

This should calm down a few souls. Now that 'a' is associated to the setA() function. There are new complaints now ... "my setA() function had only 1 argument ... where did this second argument come from ... and why do we need this ... ?"

 Let me explain this is common terms. The function setA() tries to set the value of variable "a_"  in object "a" to 10. So, we need 3 things here:  ( a,  a_ and 10 ).  a_ is a member of object 'a', so if we know 'a' we can access 'a_'. 10 is a function argument constant, which we have. So, by general sense we have all the ingredients to make this function work as expected.

 So, the point here is that C++ compiler converts 

a.setA( 10 );

to

setA( &a, 10 );

( which is the same as )

setA( pointer_to_where_object_a_is_stored, 10 );

Ok .. but who inserts the "pointer_to_where_object_a_is_stored" in the code ?   

The compiler does it. 

Ok .. how does the compiler know where object "a" is stored ?

It takes the address of "a" from a.setA( 10 );

But "a" is just an object. I didn't give any "&a" here. How does the compiler know it ?

"a" is a local variable in the current main() function's stack. So, the compiler will allocate it in main()'s stack. And this is done at compile time. If this is a bit confusing, just remember this.

"All local variables will be allocated the moment you compile you're program, and the compiler pretty much knows where they're stored."

 We'll check to see, if this is right. This requires some knowledge on how 'C' stacks work. But, I'll keep it simple. Here is the code in main() which calls a.setA(10);


0x00001cfd <main+29>:    mov    -0x18(%ebp),%eax
0x00001d00 <main+32>:    mov    %eax,(%esp)
0x00001d03 <main+35>:    movl   $0xa,0x4(%esp)
0x00001d0b <main+43>:    call   0x1e8c <A::setA(int)>


We'll decode this assembly one at a time.


0x00001cfd <main+29>:    mov    -0x18(%ebp),%eax
0x00001d00 <main+32>:    mov    %eax,(%esp)

0x00001d03 <main+35>:    movl   $0xa,0x4(%esp)
0x00001d0b <main+43>:    call   0x1e8c <A::setA(int)>


The first line here moves "some value" -0x18(%ebp) to the eax register. The second line moves that "some value" from eax register to the memory pointer to by "esp" register.  %esp is register which contains the stack pointer. Just remember it as a simple pointer, which points to some memory location in the stack.

0x00001cfd <main+29>:    mov    -0x18(%ebp),%eax
0x00001d00 <main+32>:    mov    %eax,(%esp)
0x00001d03 <main+35>:    movl   $0xa,0x4(%esp)
0x00001d0b <main+43>:    call   0x1e8c <A::setA(int)>



 0x4(%esp) is "esp pointer + 4 bytes", which points to 4 bytes past where "esp" points to. So, the above line moves 10 ( 0xa ) to "esp pointer + 4 bytes". So. here is the pictorial representation of the 3 lines of code:
 
 __________________
|                  |
|        10        |
|_________________ | <------- esp + 4 bytes
|                  |
|    some_value    |
|__________________| <------- esp + 0 bytes



0x00001cfd <main+29>:    mov    -0x18(%ebp),%eax
0x00001d00 <main+32>:    mov    %eax,(%esp)
0x00001d03 <main+35>:    movl   $0xa,0x4(%esp)
0x00001d0b <main+43>:    call   0x1e8c <A::setA(int)>


And the fourth line calls the function A::setA(int). And whenever a function is called, the arguments for the function are expected to be in the stack ( where 'esp' points to ). esp + 0 is first arg, esp + 4 is second arg, esp+ 8 is third arg etc. So, actually setA() is a normal 'C' function which is called with args ( some_value, 10 ). This some_value is the address of the object 'a'.  C++ folks tend to call it the this pointer.

If you see closely, the "this" pointer is the only difference between a normal 'C' function and a 'C++' member function. The "this" pointer has access to the object we're talking about, hence it gives the impression that it is tied to the object. So, whenever someone says, that a static C++ function doesn't have access, it means that the compiler will not add the "this" pointer to those functions, and hence the static function cannot do anything on the object as such.

The lesson to take from here is that next time when you think about classes, don't think them as a box with ( variables + functions ) packed in. Think it as a box with only variables. The functions are pretty much outside the box, with their first argument pointing to your variable box. So, if you create 10 objects of type A, there are 10 variable boxes ( 4 bytes each for type int ), and there are 2 functions outside the box, whose first argument can point to any of the 10 objects. Which object it points to depends upon which object this function is called upon.

4. Class access specifiers:

When i got introduced to C++, I too had the notion of having ( variable + function ) boxes flying in my memory. If I try to remember why, it dates back to when I got introduced to C++ access specifiers. When someone says that a function in the base class is private, and when a derived class inherits it, I don't have any easy way to capture that, other than thinking that the "private function" is present in the "base class box" and not present in the "derived class box". Now that we know how to handle C++ functions, we'll try to explore a bit on how access specifiers actually work in C++ !!

Let us consider the following simple example.


class A {
public:
  int aPublic_;
private:
  int aPrivate_;
public:
  A() {
    aPublic_ = 100;
    aPrivate_ = 200;
  }
};

int main() {
  A a;

  return 0;
}



The disassembly of A::A() is shown below:

(gdb) disassemble *0x201c
Dump of assembler code for function A::A():
0x00001f66 <A::A()+00>:    push   %ebp
0x00001f67 <A::A()+01>:    mov    %esp,%ebp
0x00001f69 <A::A()+03>:    sub    $0x4,%esp
0x00001f6c <A::A()+06>:    mov    0x8(%ebp),%eax
0x00001f6f <A::A()+09>:    mov    %eax,-0x4(%ebp)
0x00001f72 <A::A()+12>:    mov    -0x4(%ebp),%eax
0x00001f75 <A::A()+15>:    movl   $0x64,(%eax)
0x00001f7b <A::A()+21>:    mov    -0x4(%ebp),%eax
0x00001f7e <A::A()+24>:    movl   $0xc8,0x4(%eax)
0x00001f85 <A::A()+31>:    add    $0x4,%esp
0x00001f88 <A::A()+34>:    pop    %ebp
0x00001f89 <A::A()+35>:    ret   
End of assembler dump.

Just focus on the 2 highlighted lines above. The first line moves 100 ( 0x64 ) to some memory location. The second line moves 200 ( 0xc8 ) to 4 bytes past the location where 100 was stored. This should sound very familier to C programmers. Yes, this is analogous to the below code:


struct A {
  int aPublic_;
  int aPrivate_;
};

   
int main() {
   A a;


   a.aPublic_ = 100;
   a.aPrivate_ = 200;
};


The point to take from here is that there is nothing called public / private / protected in the above highlighted lines. aPublic_ and aPrivate_ are treated the same. C++ access specifiers are a compiler firewall only. Once things get past the compiler, you can pretty much imagine that things are pretty close to the "C" world. Have you ever encountered a runtime error complaining that some data member is private, and cannot be accessed ? Nope ! The C++ compiler enforces this requirement on the code while compiling, so that invalid combinations of functions and data are avoided, thereby giving more control, thus indirectly enforcing good design. One more quick example to clarify things. Consider the following class:


class A {
public:
  int aPublic_;
  A() : aPublic_( 0xa ), aPrivate_( 0xb ) { }

private:
  int aPrivate_;
};

class B : private A {
public:
  int bPublic_;
  B() : bPublic_ ( 0xc ), bPrivate_( 0xd ) { }

private:
  int bPrivate_;
};

class C : private B {
};

int main() {
  C c;

  return 0;
}


We create an instance of "class C". Conceptually, "class C" doesn't have any data members, and by inheriting "class B" privately, it has a single private data member "bPublic_". Below is the binary representation of instance "c" as laid in memory.

(gdb) print sizeof(c)
$1 = 16
(gdb) x/4xw &c
0xbffffb30:    0x0000000a    0x0000000b    0x0000000c    0x0000000d

We just analyze the instance variable "c" after it has been created. The total size of "c" is the cumulative size of all data members  ( 4 int = 16 bytes ). They are in the exact order as in the class declaration. So, data members in C++ classes can be just added up as normal 'C' structures. By preventing illegal access, the compiler gives the perspective of "access specifiers". The same logic can be extended to functions too.


Ok .... in this part, we've analyzed the class structure internals, and how member functions and access specifiers work internally. This completes the part 1 of our analysis. C++ class internals - part 2/2 analyzes the internals of constructors and destructors in detail.

2 comments:

  1. c Object doesn't have access to the private members of both class A and B. If that is the case how can the size of the C's object be equal to the size of the 4 ints? Shouldn't it be the size of 1 int?

    ReplyDelete
  2. A pubic function in Class B, say setBPrivate() can call another public function in Class A, say setAPrivate() has access to aPrivate_;

    Hence instance of Class C has 4 int = 16 bytes.

    ReplyDelete