C 静态库不能相互识别 - 为什么我得到 "undefined reference to `function`"? (链接器错误)

C static libraries do not recognize each other - why do I get "undefined reference to `function`"? (linker error)

所以,我做了一个这样的静态库:

liba.h
-----
#ifndef LIBA_H
#define LIBA_H

void do_something();

#endif

liba.c
-----
#include "liba.h"
#include <stdio.h>

void do_something() {
    printf("liba\n");
}

还有一个类似的静态库:

libb.h
-----
#ifndef LIBB_H
#define LIBB_H

void do_something_else();

#endif

libb.c
-----
#include "liba.h"
#include "libb.h"
#include <stdio.h>

void do_something_else() {
    do_something(); /* this is supposed to execute from liba */
    printf("libb\n");
}

还有一个简单的主文件:

main.c
-----
#include "liba.h"
#include "libb.h"

int main() {
    do_something_else();
}

静态库编译为:

gcc -c liba.c
gcc -c libb.c -I<path_to_liba.h>

ar rcs liba.a liba.o
ar rcs libb.a libb.o

主程序编译为:

gcc main.c -I<path_to_liba.h> -I<path_to_libb.h>
    -L<path_to_liba.a> -L<path_to_libb.a> -la -lb

当我尝试编译主程序时出现 link 错误:

libb/libb.a(libb.o): In function `do_something_else':
libb.c:(.text+0x14): undefined reference to `do_something'
collect2: error: ld returned 1 exit status

出于某些奇怪的原因,将我的 main.c 文件更改为此文件时它起作用了:

main.c
-----
#include "liba.h"
#include "libb.h"

int main() {
    do_something();
    do_something_else();
}

这不仅编译,而且给出了预期的输出:

Output:
    liba
    liba
    libb

如果不从主文件中调用 do_something,我该如何 运行 do_something_else?我显然做错了什么。

static 库链接时 - 位置很重要!在你的情况下 libb 依赖于 liba,因此,依赖库应该放在使用它的人之后。

所以,试试这个方法:

gcc main.c -I<path_to_liba.h> -I<path_to_libb.h>
    -L<path_to_liba.a> -L<path_to_libb.a> -lb -la

或者甚至这样,如果你不想关心顺序:

gcc main.c -I<path_to_liba.h> -I<path_to_libb.h>
    -L<path_to_liba.a> -L<path_to_libb.a>
    -Wl,--whole-archive -la -lb -Wl,--no-whole-archive

然而,在这种情况下,整个库都将被链接,即使它根本没有被使用。

快速解决

更改指定静态库的顺序。

说明

对于静态库,指定库的顺序很重要。原因是如果目标文件解析了以前未解析的符号,链接器将仅包含静态库中的目标文件(请记住,这些基本上只是目标文件的存档)。

以你的例子和顺序liba.a libb.a:

编译main.c 给出一个带有未解析符号do_something_else 的目标文件。现在,链接器打开 liba.a,查看包含在其中的单个目标文件 liba.o 中导出的符号,发现 do_something_else 没有匹配项(因为只有 do_somethingliba.o).因此,仍然需要解析 do_something_else,链接器查看下一个库 libb.a,查看从 libb.o 导出的符号,找到 do_something_else 并因此添加 libb.o 到将被链接的目标文件。 libb.o 包含符号 do_something 的未解析引用,因此链接器会尝试解析它。首先,它在从中获取目标文件 libb.o 的库中查找是否有另一个解析该符号的目标文件。然后它尝试使用下一个库/目标文件之一解析该符号。由于库 libb.a 中既没有另一个目标文件,也没有要查看的其他库/目标文件,因此链接器失败并显示未解析的符号 do_something.

当您使用订单libb.a liba.a时:

来自 main.o 的未解析符号 do_something_else 通过包含来自 libb.alibb.o 来解析。这会添加未解析的符号 do_something,然后可以通过包含 liba.a 中的 liba.o 来解析它。至此,链接成功。

理由

为什么不让链接器在所有库中搜索新的未解析符号?我在这里只能猜测这部分,但也提供一个合理的用例:

按照指定的顺序在库中搜索一次很简单。既要执行,又要理解。另外,考虑一下:

// main.c
#include <stdio.h>
int calculate(int value);
int main() {
  printf("%d\n", calculate(42));
}

现在有一个库 libsuperfast.a,其中包含(在其他代码中)calculate 的实现,它依赖于某些特定于平台的东西,并且仅在该平台上有条件地编译。但是您也想支持其他平台。因此,您手动实现 calculate 并将其放入 libslowashell.a。链接喜欢

$CC -o app main.o libsuperfast.a libslowashell.a

如果可用,将包括快速代码,否则将包括慢速代码。为了便携性做了类似的事情,例如在 gnulib.

其他解决方案

  • 无条件包含整个静态库,使用-Wl,--whole-archive
  • 使用libtool。允许您创建静态库和共享库,而不必过多担心平台细节。并为您处理库之间的依赖关系。
  • 在创建 libb.a 时手动解决对 liba.a 的依赖。为此,首先从必须解析其对 liba.a 的引用的文件创建一个可重定位目标文件,并与 liba.a 链接,然后从该部分链接的文件(可能还有其他文件)创建库:

    ld -r libb.o liba.a -o libb-partial.o
    ar rcs libb.a libb-partial.o # possibly others
    
  • 使用-Wl,--start-group libb.a liba.a -Wl,--end-group。这会导致链接器重复搜索库,直到不再有未解析的符号或不再有可以从库中解析的符号。