バックグラウンド
最近、私の同僚が、ライブラリの古いバージョンのヘッダー ファイルが使用されているという問題に遭遇しました。その結果、C++ で仮想関数を呼び出すために生成されたコードが、クラスの仮想関数ルックアップ テーブル ( vtable ) 内の間違ったオフセットを参照していました。
残念ながら、このエラーはコンパイル中にキャッチされませんでした。
質問
すべての通常の関数は、正しい関数 (正しいオーバーロード バリアントを含む) がリンカによって選択されるように、マングルされた名前を使用してリンクされます。同様に、オブジェクト ファイルまたはライブラリには、C++ クラスのvtable内の関数に関するシンボリック情報が含まれていると想像できます。
g++
リンク中に C++ コンパイラ (または Visual Studio など) が仮想関数の呼び出しを型チェックできるようにする方法はありますか?
例
簡単なテスト例を次に示します。この単純なヘッダー ファイルと関連する実装を想像してください。
ベース.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
派生.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
ここにメインプログラムがあります:
メイン.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;
}
正しいヘッダーを使用して「ライブラリ」をコンパイルし、次に間違ったヘッダーを使用してメイン プログラムをコンパイルおよびリンクすると…</p>
$ g++ -c Derived.cpp
$ g++ Main.cpp Derived.o -o Main
…その後、 への仮想呼び出しがvtableh()
で間違ったインデックスを使用するため、呼び出しは実際には:g()
$ ./Main
Derived::f()
f() returned: 1
Derived::g()
h() returned: 2
この小さな例では、関数g()
と関数h()
は同じシグネチャを持っているため、「唯一の」問題は間違った関数が呼び出されていることです (これはそれ自体が悪いことであり、完全に気付かれない可能性があります)。たとえば、Pascal 呼び出し規則が使用されている Windows 上の DLL で関数を呼び出す場合 (呼び出し元が引数をプッシュし、呼び出し先が戻る前に引数をポップする)。