linker — статическая проверка типов виртуальных функций во время компиляции в Stack Overflow

Недавно мой коллега столкнулся с проблемой, когда использовалась старая версия файла заголовка для библиотеки. В результате код, сгенерированный для вызова виртуальная функция в 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 (вызывающая сторона выдвигает аргументы, а вызываемая сторона выдает их перед возвратом).

6

Решение

Краткий ответ на ваш вопрос — нет. Основная проблема заключается в том, что смещения для вызываемых функций вычисляются во время компиляции; поэтому, если ваш код вызывающего абонента был скомпилирован с (неправильным) заголовочным файлом, который включал «virtual int g () const», то ваш main.o будет иметь все ссылки на h (), смещенные присутствием g (). Но ваша библиотека скомпилирована с правильным заголовочным файлом, поэтому функции g () нет, поэтому смещение h () в Derived.o будет отличаться от main.o

Это не вопрос проверки типов вызовов виртуальных функций — это «ограничение», основанное на том факте, что компилятор C ++ выполняет вычисление смещения функции во время компиляции, а не во время выполнения.

Вы можете обойти эту проблему, используя dl_open вместо прямых вызовов функций и динамически связывая вашу библиотеку вместо статического связывания.

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]