2016-03-07 25 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++课程)。无论如何,感谢您不厌其烦地发布答案。 –