我问了一个在线法学硕士,它说:
是的,绝对如此。即使构建调用依赖库中函数的静态库,也需要链接到依赖库。
但是我尝试构建一个调用另一个函数的静态库,我所做的只是包含头文件,然后调用该函数。它在 Linux 上使用 GCC 构建,没有任何关于未定义引用或其他问题的提示。
假设:
我的静态库:
#include "MyExternalFunction.h"
void my_static_library_function()
{
MyExternalFunction();
}
上述编译过程顺利,编译器没有任何抱怨。
然后我创建一个可执行文件,主要内容:
void my_static_library_function();
int main()
{
my_static_library_function();
return 0;
}
我将主可执行文件链接到我的静态库(显然,因为我调用了它的函数),然后它抱怨 MyExternalFunction 未定义,因此可执行文件必须链接该函数,但静态库则不需要。因此,静态库不必链接它调用的函数。其他符号,比如变量呢?也一样吗?静态库不需要链接任何东西,它只需要声明?
我之所以问这个问题是因为我正在构建一个静态库(libcurl),我想知道该库是否只需要包含目录到 MBedTLS(ssl 库后端)才能进行编译。
您得到的答案大体上是正确的,但是“构建”是一个模糊的术语,并没有体现出构建的不同步骤。
静态库只是一个带索引的目标文件集合。目标文件是在编译阶段生成的(我在这里指的是更严格的定义,而不是“完整构建”的同义词,尽管有时也这样使用)。它是进一步开发的依赖项,是一些用于链接的定义的有限集合。
您无需链接静态库,它只需创建存档即可生成。链接步骤需要共享库、静态库、目标文件,有时还需要一些特定内容,例如链接器脚本,您无需担心这些内容。然后,它会生成一个可执行文件或共享库,它是“最终产品”,将由操作系统安装和使用。
当操作系统加载共享库时,它会找到其中某些符号的引用,并需要将它们与内存中的实际地址关联起来。符号可以位于共享库本身,也可以预先从某个地方加载。这个“地方”通常是共享库的依赖项之一(您可以
readelf -d
在 Linux 上看到它们),但您也可以预加载一些共享库,而您的共享库对这些共享库一无所知——这需要您作为最终产品的用户付出额外的努力。还有动态链接,就是在代码中写入一个库,并在运行时的某个时刻加载它并从中挑选一些符号来使用。上面描述的整个过程仍然会执行,但你可以延迟执行,甚至可以只对实际使用的符号进行惰性执行。
关键在于,所有东西在使用时都需要定义,但您在“承诺确保提供所需的定义”方面拥有很大的灵活性。
对于目标文件(以及静态库),你只需要指定从外部引入的内容。这些是外部声明。函数声明是
extern
默认的,所以你只需要包含一个头文件int f(int x);
,它就能编译通过,但这个函数的符号将是未定义的。为了成功链接,建议在提供
f
给链接器的共享/静态库中提供 的定义。但是,如果您知道自己在做什么,您可以告诉链接器忽略未定义的符号,这样一切都会正常进行……直到您需要运行它为止。只有在那时,当加载库时(让我们忽略动态链接),您需要确保符号
f
在某处定义 - 要么在您的操作系统的加载器功能找到的共享库依赖项中(注意:不仅仅是在链接步骤中使用的那些!),要么在您明确提供给您的进程的库中。TL;DR 是基本的、良好的软件开发:您需要声明来编译,定义来链接。