2016-03-07 26 views
1

我想將最初寫在Mac llvm上的一些C++代碼移植到Windows Cygwin gcc。在這個項目中,我靜態鏈接有兩個庫的exe文件(我使用cmake):C++庫交叉依賴關係 - 從llvm移植到gcc

add_executable(myexe main.cc) 
target_link_libraries(myexe lib1 lib2) 

lib1還有一類,即聲明瞭一個虛擬方法:

lib1/Class1.h:

class Class1 
{ 
public: 
    void method1(); 
    virtual void method2(); 
}; 

lib1/Class1.cpp:

#include "Class1.h" 
void Class1::method1() { 
    // do work 
} 
// Note that method2 is not defined! 

不從lib1調用,所以這工作正常。

Class1::method2lib2定義:

lib2/Class2.h:

#include "Class1.h" 

class Class2 
{ 
private: 
    Class1 c1; 
public: 
    void call_c1(); 
}; 

in lib2/Class2.cpp:

#include "Class2.h" 

void Class1::method2() { 
    // do some other work 
} 

void Class2::call_c1() { 
    c1.method2(); 
} 

所有這一切,當我編譯和在MacOS下LLVM鏈接它工作得很好。當我嘗試在Windows/Cygwin上使用gcc進行編譯時,遇到各種鏈接器錯誤,如undefined reference to vtableundefined reference to 'Class1:method2'。實際的錯誤取決於在target_link_libraries調用庫的順序。

是否有任何命令行選項可以傳遞給gcc/cmake以使其工作?或者,最好在Windows上考慮另一個工具鏈?我現在實際上在兩個平臺上都使用IntelliJ CLion。

在此先感謝您的幫助。

+0

你舉的例子正常工作在我的cygwin的安裝,無論是你的CMake的文件是錯誤的或你的例子並不恰當代表你的榜樣。 請檢查您在機器上發佈的示例。也發佈你的cmake文件。提到你的g ++版本可能會有所幫助。 – tejas

+0

我不知道vtbl在哪個編譯單元中發出,但clang和gcc之間可能會有所不同。也許海灣合作委員會創造了一種情況,兩個庫依賴於對方。鏈接(至少與GNU ld)是一個從左到右的順序,所以你必須在提供依賴庫之前放置依賴庫,並且如果鏈接lib2引入了一個庫,你可能必須多次指定libs,比如'-llib1 -llib2 -llib1' lib1的新依賴。 –

+0

@ roland-w,非常感謝您的評論。你建議在cmake的'target_link_libraries()'中多次指定libs已經解決了這個問題。如果您將評論轉換爲答案,我會將其標記爲正確。 –

回答

1

編譯器可能發出用於Class1的VTBL,它包含一個Class1::method2參考,lib1內。如果來自lib2的一個編譯單元(即目標文件)定義爲method1,而另一個編譯單元指向Class1,則這些庫變得相互依賴。由於鏈接器(至少GNU ld)默認以單通模式工作,所以必須指定lib2兩次,一次在lib1之前,一次。 cmake指令因此是target_link_libraries(myexe lib2 lib1 lib2)

GNU LD也可以通過在顯著聯的性能損失與--start-group--end-group —包圍他們解決一組庫中所有的依賴關係。你可以通過gcc通過-Wl,--start-group等傳遞給他們,但我不知道如何讓cmake來做到這一點。

這背後的原因是,庫不鏈接爲一個整體,但是在編譯單元的基礎,因此,只有那些真正在執行需要最終建立一個圖書館的部分。在本例中,當鏈接lib2時,從主程序引用Class2會導致未解決的對Class1 vtbl的引用。鏈接lib1滿足此參考,但創建另一個未解決的Class1::method2,因爲此方法的地址是vtbl的一部分。所以鏈接器必須重新檢查lib2來解決這個問題。

注意,只有在LIB2編譯單元引用VTBL並不限定Class1::method2所述一個出現此問題;在這種情況下,符號定義已經存在,並且不需要第二次通過lib2。也許這就是爲什麼你的問題的評論指出你的例子工作正常。

鏈接器以單向模式工作,從左到右檢查庫,因爲此類型的相互依賴性很少,並且全分辨率會降低性能(由於歷史原因,核心內存不足並且堆棧很少的打卡不易隨機訪問)。

0

您在lib1中定義的Class1不完整 - 您將其定義爲虛擬方法,而不是純虛擬方法。

當您試圖鏈接lib1時,虛擬方法必須提供實現,否則鏈接將失敗 - 鏈接器將無法找到任何存在void Class1::method2()的實現。我不確定其他編譯器如何允許這樣做;也許這是其他編譯器的錯誤,或者你的編譯環境並不完全如你所說。

如果您標記的方法是純虛,這將改善這一狀況,因爲它將使LIB1編譯(儘管仍有更多的問題):

class Class1 
{ 
public: 
    void method1(); 
    virtual void method2() = 0; // mark the method as pure virtual. 
} 

有一些更大的問題,但 - 您試圖在與Class1.cpp不同的文件中定義Class1的方法,然後嘗試啓用Class2來編譯並在Class1中調用此方法。

它看起來像是在嘗試使用繼承 - 您希望Class1 :: method2()是純虛擬的,Class2從Class1繼承,並且Class2爲method2()提供實現。

Class1。H:

class Class1 
{ 
    public: 
     void method1(); 
     virtual void method2() = 0; 
}; 

Class1.cpp:

#include "Class1.h" 

void Class1::method1() { 
    // do work 
} 

Class2.h:

#include "Class1.h" 

class Class2 : public Class1 
{ 
    public: 
     virtual void method2(); 
}; 

Class2.cpp:

#include "Class2.h" 

//  - Pay close attention to this small but important change. 
//  V 
void Class2::method2() { 
    // do some other work 
} 

然後,而不是調用類2: :call_c1(),你會直接調用method2():

SomeFile.cpp:

#include "Class2.h" 

int main() { 
    Class2 someInstance; 

    someInstance.method2(); 
} 
+0

成員函數實際定義在哪個編譯單元中無關緊要。無論如何,鏈接時只能解決符號。 –

+0

@RolandW - 他原來的帖子似乎表明它是一個鏈接時問題:「實際錯誤取決於target_link_libraries調用中庫的排序。」我同意,編制單位無關緊要,但它對於聯繫確實很重要。那麼,我認爲提問者的方法可能會有更大的問題。 – antiduh

+0

@antiduh - 你對Class1不完整的評論是不正確的。查看原帖的評論,該示例編譯並鏈接正常。我不打算在這裏使用繼承或純虛擬方法。我同意代碼有點難看,很可能不會在任何有信譽的公司通過審查,但這是一個不同的辯論。事實上,我已經將它從練習中解放出來,成爲在線課程(儘管這不是C++課程)。無論如何,感謝您不厭其煩地發佈答案。 –