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

时间:2019-02-04 04:19:31

标签: c++ googletest

我注意到gtest提供了一种再次链接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符号,链接器方便地选择了我在hello.cpp中定义的一个主要功能。

这是怎么回事?

1 个答案:

答案 0 :(得分:2)

没有魔术在继续。您观察到的是的正常默认行为 链接器。

静态库libxy.a是以下内容的ar archive 目标文件x.oy.o,...

如果目标文件x.o出现在程序的链接器输入中,则链接器将其链接 进入程序无条件

如果静态库libxy.a出现在链接器输入中,则链接器将检查 归档以查找提供具有以下特征的符号的定义的任何目标文件: 已被链接到的文件中已被引用但尚未定义 该程序。它仅从存档和链接中提取那些目标文件(如果有) them 进入程序,就像它们被分别命名为链接器输入一样 而且根本没有提到静态库。

通常的原因是我们向静态库中的链接器提供了一组目标文件, 而不是作为单独的输入,所以 链接器将仅选择那些 需要,而不是简单地获取未解析符号引用的定义 不管是否需要,都将它们链接到程序中。

这是C 1 中的基本插图:-

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

像这样链接程序prog

$ gcc -o prog main.o libmxy.a

运行方式:

$ ./prog
x

因此,main提供的main.o的定义已链接,而另一个 mainlibmxy.a(lib_main.o)的定义被忽略。重复链接 带有一些诊断信息的照明系统。

$ 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选项要求链接器向我们显示实际在哪些文件中使用了哪些文件。 链接。 -trace-symbol=name要求链接器向我们显示其中的文件 符号name已定义或引用。链接的大多数文件都是样板 gcc默认添加到链接器命令行中。 我们构建的是:

main.o
(libmxy.a)x.o

链接器发现符号main首先在样板对象中被引用 文件/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o。然后 它在对象文件main中找到了main.o的定义,该定义已链接 无条件的。解决了main。链接器未在libmxy.a中搜索 main的另一种定义,因为它不需要一个。

main.o中,发现对x的未定义引用以及下一个链接器输入 是libmxy.a。因此,它将目标档案中的目标文件保留了一个 定义x。它找到了libmxy.a(x.o)并提取并链接了它。那是 完成。

我们提供给libmxy.a中的链接器的其他目标文件:

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

不需要。他们可能还不存在。链接是完全 与

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

有关libgtest_main.a ...

的更多信息

...事实上,这里有一个静态库,其中包含一个将链接的成员(libgtest_main.a(gtest_main.cc.o)) 即使您的链接之前未输入任何对象文件 libgtest_main.a

$ g++ -o prog -lgtest_main -pthread

成功链接,prog会运行,只是说它无关紧要。 如果-lgtest_main是第一个链接器输入,则链接器认为 它,它不可能在已经链接的文件中发现任何未定义的引用, 由于没有,因此不需要链接其中的任何目标文件 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完成。这是一个目标文件,因此将被链接 GCC会无条件地将其放置在生成的链接器命令行中的所有其他输入之前 之前。详细链接 模式(gcc -v ...)来查看。因此,实际上,总是有一个 对象文件,首先在程序的链接中, 无论您显式链接了哪些对象文件,都引用了main。如果你 在输入库之前,请勿自己输入定义main的对象文件,然后 链接器搜索库中的main定义。 libgtest_main利用了这一事实。

当然,只有针对googletest利用这一事实是可行的 链接googletest的程序,main的定义为相同


[1]选择C而不是C ++没什么区别,除了在C中我们 不必担心名称修改。