最近,我的一位同事遇到了一个问题,其中使用了旧版本的库头文件。结果是,为在C ++中调用虚函数生成的代码引用了类的虚函数查找表中的错误偏移量( vtable )。
不幸的是,在编译期间没有捕获到此错误。
所有普通函数都使用其错位名称进行链接,以确保链接器选择正确的函数(包括正确的重载变量)。同样,可以想象一个目标文件或库可以包含有关C ++类的 vtable 中的函数的符号信息。
有没有办法让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()
的虚拟调用使用 vtable 中的错误索引,因此调用实际上转到g()
:
$ ./Main
Derived::f()
f() returned: 1
Derived::g()
h() returned: 2
在这个小例子中,函数g()
和h()
具有相同的签名,因此出错的“唯一”事情是调用错误的函数(这本身很糟糕,可能完全不被注意),但如果签名不同,这可能(并且已经看到)导致堆栈损坏 - 例如,在使用Pascal调用约定的Windows上调用DLL中的函数时(调用者推送参数并且callee在返回之前弹出它们)。
答案 0 :(得分:0)
你问题的简短回答是否定的。基本问题是在编译时计算被调用函数的偏移量;因此,如果您的调用者代码是使用包含&#34; virtual int g()const&#34;的(不正确的)头文件编译的,那么您的main.o将通过g的存在对h()的所有引用进行偏移( )。但是您的库已使用正确的头文件进行编译,因此没有函数g(),因此Derived.o中h()的偏移量将与main.o中的偏移量不同。
这不是对虚拟功能的强制调用的问题 - 这是一个限制&#34;基于C ++编译器执行编译时函数偏移计算而不是运行时的事实。
您可以通过使用dl_open而不是直接函数调用并动态链接库来解决此问题,而不是静态链接它。