链接两个符号。一个在存档文件中定义

linking with two symbols. one defined in an archive file

我注意到 gtest 提供了一种再次 link gtest_main 的方法,因此最终用户不需要编写自己的 main 函数。这以下列方式工作。 (一个名为 hello.cpp 的小示例文件)

#include <gtest/gtest.h>

TEST(Hello, Basic) {}

可以这样编译:

g++ hello.cpp -lgtest -lgtest_main

一切顺利。这样做的原因是在 gtest_main.cc 中定义了一个 main 函数,从中生成 libgtest_main.a

事情是这样的。如果我将 hello.cpp 更改为

#include <gtest/gtest.h>

TEST(Hello, Basic) {}

int main(int argc, char** argv) {
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

一切仍然在同一个命令行下工作!现在有两个 main 符号,linker 方便地选择了我在 hello.cpp 中定义的一个主要功能。

这里发生了什么魔法?

没有魔法发生。您观察到的是正常的默认行为 linker.

静态库 libxy.aar archive 对象文件 x.oy.o、...

如果目标文件 x.o 出现在程序的 linker 输入中,linker links 它 进入程序无条件.

如果静态库 libxy.a 出现在 linker 输入中,linker 检查 archive 以查找为具有以下符号的符号提供定义的任何目标文件 已在已 link 编辑到的文件中引用但尚未定义 该程序。它只从存档和 links 中提取那些目标文件(如果有的话) 他们进入程序就像他们被单独命名为linker输入一样 根本没有提到静态库。

我们在静态库中向 linker 提供一组目标文件的通常原因, 这样 linker 将select 需要获取未解析符号引用的定义,而不是简单地 link无论是否需要,都将它们全部添加到程序中。

这是 C1 中的基本说明:-

main.c

extern void x(void);

int main(void)
{
    x();
    return 0;
}

lib_main.c

extern void y(void);

int main(void)
{
    y();
    return 0;
}

x.c

#include <stdio.h>

void x(void)
{
    puts(__func__);
}

y.c

#include <stdio.h>

void y(void)
{
    puts(__func__);
}

将所有这些编译成目标文件:

$ gcc -Wall -c main.c lib_main.c x.c y.c

制作一个包含lib_main.ox.oy.o的静态库:

$ ar rcs libmxy.a lib_main.o x.o y.o

Link 一个程序 prog 像这样:

$ gcc -o prog main.o libmxy.a

它运行就像:

$ ./prog
x

所以 main.o 提供的 main 的定义被 linked 和另一个 libmxy.a(lib_main.o)main 的定义被忽略。重复 link 年龄 通过一些诊断可以阐明更多信息。

$ gcc -o prog main.o libmxy.a -Wl,-trace,-trace-symbol=main,-trace-symbol=x
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
main.o
(libmxy.a)x.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o: reference to main
main.o: definition of main
main.o: reference to x
libmxy.a(x.o): definition of x

-trace 选项要求 linker 向我们展示实际使用了哪些文件 link年龄。 -trace-symbol=name 要求 linker 向我们展示其中的文件 已定义或引用符号 name。大多数文件 linked 都是样板文件 gcc 默认添加到 linker 命令行。 我们建造的是:

main.o
(libmxy.a)x.o

linker 找到了样板对象中首次引用的符号 main 文件 /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o。然后 它在目标文件 main.o 中找到了 main 的定义,该文件被 linked 无条件地。这解决了 main。 linker 没有搜索 libmxy.a main 的另一个定义,因为它不需要。

main.o 它找到了对 x 和下一个 linker 输入的未定义引用 是 libmxy.a。所以它在那个档案中搜索目标文件,寻找一个 定义 x。它找到 libmxy.a(x.o) 并提取并 link 编辑它。然后是 完成。

我们提供libmxy.a中的linker的其他目标文件:

libmxy.a(lib_main.o)
libmxy.a(y.o)

不需要。它们还不如不存在。 link年龄正好 等同于:

$ gcc -o prog main.o x.o
$ ./prog
x

libgtest_main.a有什么比较有趣的...

... 是事实,这里您有一个静态库,其中包含一个成员 (libgtest_main.a(gtest_main.cc.o)), 将被 linked 进入你的程序,即使你的 linkage 之前没有输入 any 目标文件 libgtest_main.a:

$ g++ -o prog -lgtest_main -pthread

links成功了,和prog会运行只是说没关系。 如果 -lgtest_main 是第一个 linker 输入,那么当 linker 考虑 它,它不可能在已经 linked 的文件中发现任何未定义的引用, 因为有 none,因此不需要 link 内的任何目标文件 libgtest_main.a。但确实如此,而且这种行为可能被描述为有点 魔法.

但是我们已经在诊断输出中看到了解释:

$ gcc -o prog main.o libmxy.a -Wl,-trace,-trace-symbol=main,-trace-symbol=x

这告诉我们 main 首次在 /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o 中被引用。

该样板目标文件是 GCC C 运行时间启动代码,它为程序执行标准初始化 执行并通过调用 main 结束。这是一个 目标文件 ,因此它将被 linked 无条件地,GCC 将其放置在 之前 生成的 linker 命令行中的所有其他输入。 Link 详细 模式 (gcc -v ...) 来查看。所以实际上总是一个目标文件,首先在程序的linkage, 引用 main,无论您明确 link 是什么目标文件。如果你 在输入库之前不要自己输入定义 main 的目标文件,然后 linker 在库中搜索 main 的定义。 libgtest_main 利用了这一事实。

当然,只有在 googletest 中利用这个事实才是实际的,因为对于所有正常 link googletest 的程序,main 的定义相同


[1] 选择 C ​​而不是 C++ 没有区别,除了在 C 中我们 不必为名称修改而烦恼。