Недавно мой коллега столкнулся с проблемой, когда использовалась старая версия файла заголовка для библиотеки. В результате код, сгенерированный для вызова виртуальная функция в C ++ ссылается на неправильное смещение в таблице поиска виртуальных функций для класса ( виртуальные таблицы).
К сожалению, эта ошибка не была обнаружена во время компиляции.
Все обычные функции связаны с использованием их искаженных имен, что гарантирует компоновщику правильную функцию (включая правильный вариант перегрузки). Аналогично, можно представить, что объектный файл или библиотека могут содержать символическую информацию о функциях в виртуальные таблицы для класса C ++.
Есть ли способ позволить компилятору C ++ (скажем, g++
или Visual Studio) проверка типа вызовов виртуальной функции во время связывания?
Вот простой тестовый пример. Представьте себе этот простой заголовочный файл и связанную с ним реализацию:
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();
}
}
Теперь представьте, что программа использует неправильную / старую версию файла заголовка, в которой отсутствует виртуальная функция в середине:
BaseWrong.hpp
#ifndef BASEWRONG_HPP
#define BASEWRONG_HPP
namespace Test
{
class Base
{
public:
virtual int f() const = 0;
// Missing: virtual int g() const = 0;
virtual int h() const = 0;
};
class BaseFactory
{
public:
static const Base* createBase();
};
}
#endif
Итак, у нас есть основная программа:
main.cpp
// Including the _wrong_ version of the header!
#include "BaseWrong.hpp"
#include <iostream>
using namespace std;
int main()
{
const Test::Base* base = Test::BaseFactory::createBase();
const int fres = base->f();
cout << "f() returned: " << fres << endl;
const int hres = base->h();
cout << "h() returned: " << hres << endl;
return 0;
}
Когда мы собираем «библиотеку», используя правильный заголовок, а затем скомпилировать и связать основную программу, используя неправильно заголовок …
$ g++ -c Derived.cpp
$ g++ Main.cpp Derived.o -o Main
… тогда виртуальный вызов h()
использует неправильный индекс в виртуальные таблицы так что вызов на самом деле идет g()
:
$ ./Main
Derived::f()
f() returned: 1
Derived::g()
h() returned: 2
В этом небольшом примере функции g()
а также h()
имеют одну и ту же сигнатуру, поэтому «единственной» ошибкой является то, что вызывается неправильная функция (которая сама по себе плохая и может остаться незамеченной), но если сигнатуры отличаются, это может привести (и было замечено) к повреждению стека — например, при вызове функции в DLL в Windows, где используется соглашение о вызовах Pascal (вызывающая сторона выдвигает аргументы, а вызываемая сторона выдает их перед возвратом).
Краткий ответ на ваш вопрос — нет. Основная проблема заключается в том, что смещения для вызываемых функций вычисляются во время компиляции; поэтому, если ваш код вызывающего абонента был скомпилирован с (неправильным) заголовочным файлом, который включал «virtual int g () const», то ваш main.o будет иметь все ссылки на h (), смещенные присутствием g (). Но ваша библиотека скомпилирована с правильным заголовочным файлом, поэтому функции g () нет, поэтому смещение h () в Derived.o будет отличаться от main.o
Это не вопрос проверки типов вызовов виртуальных функций — это «ограничение», основанное на том факте, что компилятор C ++ выполняет вычисление смещения функции во время компиляции, а не во время выполнения.
Вы можете обойти эту проблему, используя dl_open вместо прямых вызовов функций и динамически связывая вашу библиотеку вместо статического связывания.
Других решений пока нет …