C++ 库交叉依赖 - 从 llvm 移植到 gcc
C++ library cross dependencies - porting from llvm to gcc
我正在尝试将我最初在 Mac llvm 上编写的一些 C++ 代码移植到 Windows Cygwin gcc。在这个项目中,我静态 linking 一个带有两个库的 exe(我正在使用 cmake):
add_executable(myexe main.cc)
target_link_libraries(myexe lib1 lib2)
在lib1
中有一个class,它声明了一个虚方法:
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!
Class1::method2
不是从 lib1
调用的,所以这工作正常。
Class1::method2
定义在 lib2
:
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 link 编译和 link 时,所有这些工作正常。当我尝试在 Windows/Cygwin 上使用 gcc 构建它时,我 运行 遇到各种 link 错误,例如 undefined reference to vtable
或 undefined reference to 'Class1:method2'
。实际错误取决于 target_link_libraries
调用中库的顺序。
是否有任何命令行选项可以传递给 gcc/cmake 以使其正常工作?或者考虑 Windows 上的另一个工具链可能更好?我现在实际上在两个平台上都使用 IntelliJ CLion。
在此先感谢您的帮助。
您在 lib1 中对 Class1
的定义不完整 - 您将其定义为虚方法,而不是纯虚方法。
当您尝试 link lib1 时,虚拟方法必须提供一个实现,否则 linking 将失败 - linker 将无法找到任何实现void Class1::method2()
因为 none 存在。我不确定其他编译器如何允许这样做;可能是其他编译器的bug,或者你的编译环境和你说的不完全一样。
如果您将该方法标记为纯虚拟方法,这会改善这种情况,因为它将允许 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
}
然后,您可以直接调用 method2(),而不是调用 Class2::call_c1():
SomeFile.cpp:
#include "Class2.h"
int main() {
Class2 someInstance;
someInstance.method2();
}
编译器可能发出 Class1
的 vtbl,其中包含对 Class1::method2
的引用,指向 lib1。如果 lib2 中的一个编译单元(即目标文件)定义了 method1
而另一个引用了 Class1
,那么库就会相互依赖。由于链接器(至少是 GNU ld)在默认情况下以一次性模式工作,因此必须两次指定 lib2,一次在 lib1 之前,一次在 lib1 之后。因此 cmake 指令是 target_link_libraries(myexe lib2 lib1 lib2)
.
GNU ld 还可以解决一组库之间的所有依赖关系,方法是用 --start-group
和 --end-group
将它们括起来——这会显着降低链接性能。您可以通过 -Wl,--start-group
等通过 gcc 传递它们。不过,我不知道如何让 cmake 做到这一点。
这背后的原因是库不是作为一个整体链接的,而是在编译单元的基础上链接的,因此只有库中实际需要的部分才会出现在可执行文件中。在目前的情况下,从主程序引用 Class2
会在链接 lib2 时导致对 Class1
vtbl 的未解析引用。链接 lib1 满足此引用,但会创建另一个未解析的 Class1::method2
,因为此方法的地址是 vtbl 的一部分。所以链接器必须重新检查 lib2 来解决这个问题。
请注意,只有在lib2中引用vtbl的编译单元不是定义Class1::method2
的编译单元时才会出现此问题;在那种情况下,符号定义已经存在,不需要第二次遍历 lib2。也许这就是为什么对您的问题的评论表明您的示例工作正常。
链接器以一次性模式工作,从左到右检查库,因为这种类型的相互依赖很少见,而且完整解析会降低性能(由于历史原因,当核心内存稀缺和成堆的穿孔卡片不容易随机访问)。
我正在尝试将我最初在 Mac llvm 上编写的一些 C++ 代码移植到 Windows Cygwin gcc。在这个项目中,我静态 linking 一个带有两个库的 exe(我正在使用 cmake):
add_executable(myexe main.cc)
target_link_libraries(myexe lib1 lib2)
在lib1
中有一个class,它声明了一个虚方法:
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!
Class1::method2
不是从 lib1
调用的,所以这工作正常。
Class1::method2
定义在 lib2
:
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 link 编译和 link 时,所有这些工作正常。当我尝试在 Windows/Cygwin 上使用 gcc 构建它时,我 运行 遇到各种 link 错误,例如 undefined reference to vtable
或 undefined reference to 'Class1:method2'
。实际错误取决于 target_link_libraries
调用中库的顺序。
是否有任何命令行选项可以传递给 gcc/cmake 以使其正常工作?或者考虑 Windows 上的另一个工具链可能更好?我现在实际上在两个平台上都使用 IntelliJ CLion。
在此先感谢您的帮助。
您在 lib1 中对 Class1
的定义不完整 - 您将其定义为虚方法,而不是纯虚方法。
当您尝试 link lib1 时,虚拟方法必须提供一个实现,否则 linking 将失败 - linker 将无法找到任何实现void Class1::method2()
因为 none 存在。我不确定其他编译器如何允许这样做;可能是其他编译器的bug,或者你的编译环境和你说的不完全一样。
如果您将该方法标记为纯虚拟方法,这会改善这种情况,因为它将允许 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
}
然后,您可以直接调用 method2(),而不是调用 Class2::call_c1():
SomeFile.cpp:
#include "Class2.h"
int main() {
Class2 someInstance;
someInstance.method2();
}
编译器可能发出 Class1
的 vtbl,其中包含对 Class1::method2
的引用,指向 lib1。如果 lib2 中的一个编译单元(即目标文件)定义了 method1
而另一个引用了 Class1
,那么库就会相互依赖。由于链接器(至少是 GNU ld)在默认情况下以一次性模式工作,因此必须两次指定 lib2,一次在 lib1 之前,一次在 lib1 之后。因此 cmake 指令是 target_link_libraries(myexe lib2 lib1 lib2)
.
GNU ld 还可以解决一组库之间的所有依赖关系,方法是用 --start-group
和 --end-group
将它们括起来——这会显着降低链接性能。您可以通过 -Wl,--start-group
等通过 gcc 传递它们。不过,我不知道如何让 cmake 做到这一点。
这背后的原因是库不是作为一个整体链接的,而是在编译单元的基础上链接的,因此只有库中实际需要的部分才会出现在可执行文件中。在目前的情况下,从主程序引用 Class2
会在链接 lib2 时导致对 Class1
vtbl 的未解析引用。链接 lib1 满足此引用,但会创建另一个未解析的 Class1::method2
,因为此方法的地址是 vtbl 的一部分。所以链接器必须重新检查 lib2 来解决这个问题。
请注意,只有在lib2中引用vtbl的编译单元不是定义Class1::method2
的编译单元时才会出现此问题;在那种情况下,符号定义已经存在,不需要第二次遍历 lib2。也许这就是为什么对您的问题的评论表明您的示例工作正常。
链接器以一次性模式工作,从左到右检查库,因为这种类型的相互依赖很少见,而且完整解析会降低性能(由于历史原因,当核心内存稀缺和成堆的穿孔卡片不容易随机访问)。