Section 8.12 Object-Oriented Programming with C
In the previous section, we have seen the differences between an object-oriented language and a purely imperative language like C. In this section, we outline how object-orientation can be realized in C.
Consider the example in Listing 8.10.3 and Listing 8.10.2. For simplicity's sake, we assume that a class can only inherit from/implement a single class/interface. Allowing to implement several interfaces would slightly complicate the implementation without contributing fundamentally to the understanding.
Instead of writing functions where every class is handled as a branch (like in Listing 8.10.2), we create a function for every method. For every class, there is a table of functions (more specifically: function pointers). This table is called virtual method table, which is often abbreviated as VMT, vtable, or vtab. The VMT contains for each method of the class (and every super class) the address of the corresponding function. Since every class has its own VMT, the address of the VMT identifies the class uniquely. It corresponds to the “tag”
op
of the struct exp_t
in Listing 8.10.2. Every object obtains during its initialization (where the concrete type is known) a pointer to the VMT of the class in a field that is hidden to the programmer (vtab
in Listing 8.12.1). Since we need to access the VMT without knowing the concrete type, the pointer to the VMT is at the same position for every object (usually at offset 0). This allows us to identify the concrete type of any object at run time.The address of the virtual method required at a given call site results from a two-step process: First, the right entry in the VMT needs to be determined. This information is given by the method name (and the signature, in case of method overloading) and can therefore be derived statically at compile time. In our example in Listing 8.12.1, the selected entry corresponds to a field in the
...vtab_t
structs. Java's static semantics guarantees that there is an entry for the called method in every possible VMT. In the second step, the concrete VMT is determined. Since the VMT address has been written to the object's hidden vtab
field during initialization, we only need to load this address at run time to obtain the right VMT.One such call occurs in
exp_add_eval
in Listing 8.12.1 in the return
statement. The expression l->vtab->eval
determines the address of the overridden method eval
via the VMT.