vtables under the surface | Episode 1 - Concepts
Tags: cpp, computerscience, todayisearched, todayilearned
Published: 2024-01-05 09:04:49

If you've been around C++ for a while, you've likely come across the terms "vtable", "virtual table" or "[virtual method table](https://en.wikipedia.org/wiki/Virtual_method_table)". Vtables are not part of the C++ standard, even though this concept pops up almost immediately when you try to understand how virtual functions actually work in C++. Indeed, vtables are the most common implementation of polymorphism in C++.

You may also have already encountered a cryptic compilation error like "undefined reference to `vtable for MyClass`". This may have left you perplexed because you didn't explicitly create anything named "vtable" in your code. This error signals that, under the hood, the compiler generates vtables to handle virtual functions. If you're curious about this error, here is a [good discussion](https://stackoverflow.com/questions/3065154/undefined-reference-to-vtable) on stackoverflow (the [second answer](https://stackoverflow.com/a/57504289) is particularly enlightening).

In this series, we're going to the lion's den and explore how a vtable works at the byte and assembly levels.

## Disclaimer

As mentioned in the introduction, we'll be discussing implementation details. By definition, these details may vary from one compiler to another or from one OS to another. They may even change in the next version of the same compiler for the same OS.

However, most major compilers (except MSVC) follow the [Itanium C++ ABI](https://itanium-cxx-abi.github.io/cxx-abi/):

> The Itanium C++ ABI is an ABI for C++. As an ABI, it gives precise rules for implementing the language, ensuring that separately-compiled parts of a program can successfully interoperate. Although it was initially developed for the Itanium architecture, it is not platform-specific and can be layered portably on top of an arbitrary C ABI. Accordingly, it is used as the standard C++ ABI for many major operating systems on all major architectures, and is implemented in many major C++ compilers, including GCC and Clang.

The ABI has a [section about vtables](https://itanium-cxx-abi.github.io/cxx-abi/abi.html#vtable), so using any compiler following this ABI should yield similar implementation details.

In this series, I will use GCC 12.2.0 on Debian BookWorm (unless stated otherwise).

## The Code

Here is a code that will serve as the foundation for the discussion in the series (at least for the first episodes). It is split into 3 different files:

`code.hpp`:

```c++
#pragma once

struct Base {
    virtual void foo() const;
    virtual void bar() const;
private:
    int dummy_base;
};

struct Derived : Base {
    void foo() const override;
    void bar() const override;
private:
    int dummy_derived;
};

void use(const Base&);
```

`code.cpp`:

```c++
#include "code.hpp"

#include <cstdio>

void Base::foo() const {
    std::puts("Base => foo()");
}

void Base::bar() const {
    std::puts("Base => bar()");
}

void Derived::foo() const {
    std::puts("Derived => foo()");
}

void Derived::bar() const {
    std::puts("Derived => bar()");
}

void use(const Base& obj) {
    obj.foo();
}
```

`main.cpp`:

```c++
#include "code.hpp"

int main() {
    auto obj = Derived();
    use(obj);
}
```

This code is a typical example of polymorphism using virtual functions. `use()` receives a reference to a `Base` object. At runtime, dynamic dispatch selects and calls `Derived::foo()` and the program prints `Derived => foo()`. Looking at this code, you don't see any vtables. We will analyze the generated binary and find them later in the series. 

## Build and Run

You can easily build the program with CMake:

```cmake
cmake_minimum_required(VERSION 3.25)
project(vtables)

add_executable(a.out
        main.cpp
        code.cpp
        code.hpp
)

target_compile_options(a.out PRIVATE
        -O1
)
```

From the root directory of the project:

```bash
$ cmake -B build -S .
$ cmake --build build
$ ./build/a.out 
Derived => foo()
```

Notes:

- Separated source files avoid possible compiler optimizations.
- `-O1` generates an assembly code that is much easier to read and understand than `-O0`.

## We're Ready

We are now ready. Let's dive into vtables!

> I will publish the episodes as I write them. Subscribe to be notified! 🎉