CSC447

Concepts of Programming Languages

C++ and Vtables

Instructor: James Riely

C++ and Vtables

  • Very large PL
  • Brief overview of
    • OO features
    • vtable implementation of dynamic dispatch

C++ Evolution

Reading

Which toString?

  • x has static type Animal, dynamic type Bird/Cat

class Animal              { public: string to_string() { return "Animal"; } };
class Bird: public Animal { public: string to_string() { return "Bird"; } };
class Cat:  public Animal { public: string to_string() { return "Cat"; } };

Animal *xs[] = { new Bird(), new Cat() };
for (Animal *x : xs) cout << x->to_string() << endl; 
          

$ g++ -std=c++11 -o animal1 animal1.cpp && ./animal1
Animal
Animal
          
  • Object pointer + non-virtual method = static dispatch

Which toString?

  • x has static type Animal, dynamic type Bird/Cat

class Animal              { public: virtual string to_string() { return "Animal";
class Bird: public Animal { public: virtual string to_string() { return "Bird"; }
class Cat:  public Animal { public: virtual string to_string() { return "Cat"; } 

Animal *xs[] = { new Bird(), new Cat() };
for (Animal *x : xs) cout << x->to_string() << endl; 
          

$ g++ -std=c++11 -o animal2 animal2.cpp && ./animal2
Bird
Cat
          
  • Object pointer + virtual method = dynamic dispatch

Which toString?

  • x has static type Animal, dynamic type Bird/Cat

class Animal              { public: virtual string to_string() const { return "Animal"; } };
class Bird: public Animal { public: virtual string to_string() const { return "Bird"; } };
class Cat:  public Animal { public: virtual string to_string() const { return "Cat"; } };

const Animal &x = Bird(); // no arrays of references
cout << x.to_string() << endl; 
          

$ g++ -std=c++11 -o animal2r animal2r.cpp && ./animal2r
Bird
          
  • Object reference + virtual method = dynamic dispatch

Which toString?

  • x has static type Animal, dynamic type Animal

class Animal              { public: virtual string to_string() { return "Animal";
class Bird: public Animal { public: virtual string to_string() { return "Bird"; }
class Cat:  public Animal { public: virtual string to_string() { return "Cat"; } 

Animal  xs[] = {     Bird(),     Cat() };
for (Animal  x : xs) cout <<  x.to_string() << endl; 
          

$ g++ -std=c++11 -o animal3 animal3.cpp && ./animal3
Animal
Animal
          
  • Object value = static dispatch

Object Slicing


class Animal               { char a[3]; };
class Bird : public Animal { char b[5]; };
class Cat  : public Animal { char c[7]; };

Animal  xs[] = {     Bird(),     Cat() };
cout << "sizeof(Animal) = " << sizeof(Animal)  << endl;
cout << "sizeof(Bird)   = " << sizeof(Bird)  << endl;
cout << "sizeof(Cat)    = " << sizeof(Cat)  << endl;
cout << "sizeof(xs)     = " << sizeof(xs) << endl;
          

$ g++ -std=c++11 -o animal4 animal4.cpp && ./animal4
sizeof(Animal) = 3
sizeof(Bird)   = 8
sizeof(Cat)    = 10
sizeof(xs)     = 6
          

Pointers are not sliced


class Animal               { char a[3]; };
class Bird : public Animal { char b[5]; };
class Cat  : public Animal { char c[7]; };

Animal *xs[] = { new Bird(), new Cat() };
cout << "sizeof(Animal*)= " << sizeof(Animal*) << endl;
cout << "sizeof(Bird*)  = " << sizeof(Bird*) << endl;
cout << "sizeof(Cat*)   = " << sizeof(Cat*) << endl;
cout << "sizeof(xs)     = " << sizeof(xs) << endl;
          

$ g++ -std=c++11 -o animal5 animal5.cpp && ./animal5
sizeof(Animal*)= 8
sizeof(Bird*)  = 8
sizeof(Cat*)   = 8
sizeof(xs)     = 16
          
  • Pointers are all the same size
  • No reason to slice

Methods not stored with objects


class Animal               { char a[3]; public: string to_string() { return "Animal"; } };
class Bird : public Animal { char b[5]; public: string to_string() { return "Bird"; } };
class Cat  : public Animal { char c[7]; public: string to_string() { return "Cat"; } };

cout << "sizeof(Animal) = " << sizeof(Animal)  << endl;
cout << "sizeof(Bird)   = " << sizeof(Bird)  << endl;
cout << "sizeof(Cat)    = " << sizeof(Cat)  << endl;
          

$ g++ -std=c++11 -o animal6 animal6.cpp && ./animal6
sizeof(Animal) = 3
sizeof(Bird)   = 8
sizeof(Cat)    = 10
          
  • Non-virtual method = no runtime overhead

Virtual methods stored in table


class Animal               { char a[3]; public: virtual string to_string() { return "Animal"; } };
class Bird : public Animal { char b[5]; public: virtual string to_string() { return "Bird"; } };
class Cat  : public Animal { char c[7]; public: virtual string to_string() { return "Cat"; } };

cout << "sizeof(Animal) = " << sizeof(Animal)  << endl;
cout << "sizeof(Bird)   = " << sizeof(Bird)  << endl;
cout << "sizeof(Cat)    = " << sizeof(Cat)  << endl;
          

$ g++ -std=c++11 -o animal7 animal7.cpp && ./animal7
sizeof(Animal) = 16
sizeof(Bird)   = 16
sizeof(Cat)    = 24
          
  • Virtual method = runtime overhead
  • 8 bytes for pointer, multiples of 8

One table for all virtual methods


class Animal              { char a[3]; public: virtual string to_string() { return "Animal"; } };
class Cat : public Animal { char c[7]; public: virtual string to_string() { return "Bird"; }
                                               virtual void f() { } };

cout << "sizeof(Animal) = " << sizeof(Animal)  << endl;

cout << "sizeof(Cat)    = " << sizeof(Cat)  << endl;
          

$ g++ -std=c++11 -o animal7 animal7.cpp && ./animal7
sizeof(Animal) = 16

sizeof(Cat)    = 24
          
  • One table
  • 8 bytes for pointer, multiples of 8

C++ Object Representation

  • Study object representation by looking at x86-64
    • Familiar from CSC406 or other systems prereq
    • Compile with G++ to assembly
      
      $ g++ -S singlefields.cpp
                        
  • Identify vtables supporting dynamic dispatch

Refresher: x86-64

  • 64-bit addresses/64-bit registers
    • General purpose registers: %rax, %rdx,, %rdi,...
      • %eax is half of %rax
      • First function parameter: %rdi
    • Instruction pointer: %rip
    • Base pointer: %rbp
    • Stack pointer: %rsp
  • Dereference memory location %rbp-8,
    then copy contents to %rax
    
    movq -8(%rbp), %rax
                  
    
    movl -8(%rbp), %eax
                  

Direct Calls


void greetEnglish () { printf ("Hello\n"); }
void greet        () { greetEnglish (); }
          

greetEnglish:
        ...
greet:
        pushq   %rbp
        movq    %rsp, %rbp
        callq   greetEnglish
        popq    %rbp
        retq
          

Indirect Calls


void greetEnglish () { printf ("Hello\n"); }
void greetSpanish () { printf ("Hola\n"); }

void greet (bool useEnglish) { 
  void (*p) ();  // p is a function pointer
  if (useEnglish) {
    p = &greetEnglish;  
  } else {
    p = &greetSpanish;  
  }
  (*p) ();      // invoke function p points to
}
          

greet:
	pushq	%rbp
	movq	%rsp, %rbp
	cmpl	$0, %rdi  /* rdi = useEnglish */
	je	.ELSE
	leaq	$greetEnglish(%rip), %rax
	jmp	.ENDIF
.ELSE:
	leaq	$greetSpanish(%rip), %rax
.ENDIF:
	callq	*%rax     /* rax = p */
        popq    %rbp
        retq
          

Single Inheritance: Fields


#include <iostream>
using namespace std;

class B {
public: 
  int b1;
  int b2;
};

class D : public B {
public: 
  int d1;
  int d2;
};

int main () {
  D *d = new D ();
  d->d1 = 9032;
  d->d2 = 3293;
  d->b1 = 2948;
  d->b2 = 8432;

  B *b = (B*) d;
  int x1 = b->b1;
  int x2 = b->b2;
  cout << "Values are: " << x1 << ", " << x2 << endl;
}
          

Values are: 2948, 8432
          

Single Inheritance: Methods


#include <iostream>
using namespace std;

class B {
public: 
  int b1;
  int b2;
  virtual void f1 () { cout << "B.f1 "; }
  virtual void f2 () { cout << "B.f2 "; }
};

class D : public B {
public: 
  int d1;
  int d2;
  virtual void f2 () { cout << "D.f2 "; }
  virtual void f3 () { cout << "D.f3 "; }
};

int main () {
  D *d = new D ();
  d->b1 = 2948;
  d->f1 ();
  d->f2 ();
  d->f3 ();
  B *b = (B*) d;
  b->f1 ();
  b->f2 ();
  // Not allowed, because f3 is not member function of class B:
  // b->f3 ();
}  
          

B.f1 D.f2 D.f3 B.f1 D.f2          

Multiple Inheritance: Fields


#include <iostream>
using namespace std;

class B {
public: 
  int b1;
  int b2;
};

class F {
public: 
  int f1;
  int f2;
};

class D : public B, public F {
public: 
  int d1;
  int d2;
};

int main () {
  D *d = new D ();
  d->d1 = 1419;
  d->d2 = 7292;
  d->f1 = 9032;
  d->f2 = 3293;
  d->b1 = 2948;
  d->b2 = 8432;
  B *b = (B*) d;
  int x1 = b->b1;
  int x2 = b->b2;
  F *f = (F*) d;
  int y1 = f->f1;
  int y2 = f->f2;
  cout << "Values are: " << x1 << ", " << x2 << ", " << y1 << ", " << y2 << endl;  
  cout << "d: " << d << ", b: " << b << ", f:" << f << endl;
}
          

Values are: 2948, 8432, 9032, 3293
d: 0x7f83a9d05720, b: 0x7f83a9d05720, f:0x7f83a9d05728