Background
Recently, my colleague ran into a problem when using the old version of the header file for the library. As a result, the code created to call the virtual function in C ++ referred to an incorrect offset in the virtual function lookup table for the class (vtable).
Unfortunately, this error was not detected at compile time.
Question
All common functions involve using their crippled names, which provide the correct function (including the correct overload option) chosen by the linker. Similarly, you can imagine that an object file or library may contain symbolic information about the functions in the vtable for the C ++ class.
Is it possible to allow a C ++ compiler (e.g. g++ or Visual Studio) to check calls to a virtual function during binding?
Example
Here is a simple test example. Imagine this simple header file and its associated implementation:
Base.hpp:
#ifndef BASE_HPP #define BASE_HPP namespace Test { class Base { public: virtual int f() const = 0; virtual int g() const = 0; virtual int h() const = 0; }; class BaseFactory { public: static const Base* createBase(); }; } #endif
Derived.cpp:
#include "Base.hpp" #include <iostream> using namespace std; namespace Test { class Derived : public Base { public: virtual int f() const { cout << "Derived::f()" << endl; return 1; } virtual int g() const { cout << "Derived::g()" << endl; return 2; } virtual int h() const { cout << "Derived::h()" << endl; return 3; } }; const Base* BaseFactory::createBase() { return new Derived(); } }
Now imagine that the program uses the wrong / old version of the header file, where there is no virtual function in the middle:
BaseWrong.hpp
#ifndef BASEWRONG_HPP #define BASEWRONG_HPP namespace Test { class Base { public: virtual int f() const = 0;
So, we have the main program:
main.cpp
When we compile the “library” using the correct header, and then compile and link the main program using the wrong header ...
$ g++ -c Derived.cpp $ g++ Main.cpp Derived.o -o Main
... then the virtual call to h() uses the wrong index in the vtable, so the call really goes to g() :
$ ./Main Derived::f() f() returned: 1 Derived::g() h() returned: 2
In this small example, the functions g() and h() have the same signature, so the “only” thing that goes wrong is the wrong function called (which in itself is bad and can completely disappear), but if the signatures are different, which can (and, apparently, this) lead to damage to the stack - for example, when calling a function in a DLL on Windows that uses the Pascal calling convention (the caller pushes the arguments and the caller calls them before returning).