簡體   English   中英

C ++中虛函數的編譯時靜態類型檢查

[英]Compile-time static type check of virtual functions in C++

背景

最近,我的一位同事遇到了一個問題,其中使用了舊版本的庫頭文件。 結果是,為在C ++中調用虛函數而生成的代碼引用了類( vtable )的虛函數查找表中的錯誤偏移量。

不幸的是,在編譯期間沒有捕獲到此錯誤。

所有普通函數都使用其錯位名稱進行鏈接,以確保鏈接器選擇正確的函數(包括正確的重載變量)。 同樣,可以想象一個目標文件或庫可以包含有關Ctable類的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在返回之前彈出它們)。

對你的問題的簡短回答是否定的。 基本問題是在編譯時計算被調用函數的偏移量; 因此,如果您的調用者代碼是使用包含“virtual int g()const”的(不正確的)頭文件編譯的,那么您的main.o將通過g()的存在對h()的所有引用進行偏移。 但是您的庫已經使用正確的頭文件進行編譯,因此沒有函數g(),因此Derived.o中h()的偏移量將與main.o中的偏移量不同。

這不是對虛函數進行類型調用的問題 - 這是基於C ++編譯器執行編譯時函數偏移計算而非運行時的事實的“限制”。

您可以通過使用dl_open而不是直接函數調用並動態鏈接庫來解決此問題,而不是靜態鏈接它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM