C++ C++; 背景

C++ C++; 背景,c++,linker,static-analysis,virtual-functions,one-definition-rule,C++,Linker,Static Analysis,Virtual Functions,One Definition Rule,最近,我的一位同事遇到了一个问题,即使用了库头文件的旧版本。结果是,C++中调用虚函数生成的代码在类(VTABLE)的虚函数查找表中引用了错误的偏移。 不幸的是,在编译过程中没有捕获到此错误 问题: 所有普通函数都使用其损坏的名称进行链接,以确保链接器选择正确的函数(包括正确的重载变量)。同样,可以想象一个对象文件或一个库可以包含关于C++类的VTABLE函数的符号信息。 P>是否有任何方式让C++编译器(如代码> G++或VisualStudio)类型在链接时检查对虚函数的调用? 例子 下面

最近,我的一位同事遇到了一个问题,即使用了库头文件的旧版本。结果是,C++中调用虚函数生成的代码在类(VTABLE)的虚函数查找表中引用了错误的偏移。 不幸的是,在编译过程中没有捕获到此错误

问题: 所有普通函数都使用其损坏的名称进行链接,以确保链接器选择正确的函数(包括正确的重载变量)。同样,可以想象一个对象文件或一个库可以包含关于C++类的VTABLE函数的符号信息。 <> P>是否有任何方式让C++编译器(如代码> G++<代码>或VisualStudio)类型在链接时检查对虚函数的调用?

例子 下面是一个简单的测试示例。想象一下这个简单的头文件和相关的实现:

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
#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();
  }

}
派生。cpp:

#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
#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();
  }

}
这里我们有一个主要的程序:

Main.cpp

#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
// 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;
}
…然后对
h()
的虚拟调用在vtable中使用了错误的索引,因此调用实际上转到
g()


在这个小示例中,函数
g()
h()
具有相同的签名,因此“唯一”出错的是调用了错误的函数(这本身是错误的,可能会完全被忽略),但如果签名不同,则可能(并且已经看到)导致堆栈损坏–例如。,在使用Pascal调用约定的Windows上,在DLL中调用函数时(调用者推送参数,被调用者在返回之前弹出参数)。

您的问题的简短答案是否定的。基本问题是被调用函数的偏移量是在编译时计算的;因此,如果调用方代码是使用包含“virtual int g()const”的(不正确)头文件编译的,那么main.o的所有对h()的引用都会被g()偏移。但是您的库是使用正确的头文件编译的,因此没有函数g(),因此派生.o中h()的偏移量将不同于main.o中的偏移量

<>这不是一个对虚拟函数调用类型的问题——这是一个“限制”,是基于C++编译器编译时间函数偏移量计算而不是运行时的事实。
通过使用dl_open而不是直接函数调用以及动态链接库而不是静态链接库,您可以绕过此问题。

ODR冲突通常是不可诊断的。这就是为什么标准不要求对它们进行诊断,这就是为什么它们是如此糟糕的故障模式。你的包含防护是非法的@BaummitAugen已修复。@KerrekSB当前的链接器不这样做,但没有理由认为它不可能实现,甚至难以实现。在派生类中使用重写而不是虚拟。它将检查基类是否具有该方法。