gcc 链接顺序问题

前言

之前在项目中遇到一个编译报错 “undefined reference to” 的问题,当时的解决方法是调整了库的链接顺序。但是一直没有想清楚为什么调整库的链接顺序就可以了,直到最近看了 gcc 官网给出的 “-l” 选项的说明

库的链接顺序

关于库的链接,gcc 官网是这么解释的:

https://gcc.gnu.org/onlinedocs/gcc-5.2.0/gcc/Link-Options.html#Link-Options
-l library
Search the library named library when linking. (The second alternative with the library as a separate argument is only for POSIX compliance and is not recommended.)
It makes a difference where in the command you write this option; (1)the linker searches and processes libraries and object files in the order they are specified. Thus, ‘foo.o -lz bar.o’ searches library ‘z’ after file foo.o but before bar.o. If bar.o refers to functions in ‘z’, those functions may not be loaded.
The linker searches a standard list of directories for the library, which is actually a file named liblibrary.a. The linker then uses this file as if it had been specified precisely by name.
The directories searched include several standard system directories plus any that you specify with -L.
(2)Normally the files found this way are library files—archive files whose members are object files. The linker handles an archive file by scanning through it for members which define symbols that have so far been referenced but not defined. But if the file that is found is an ordinary object file, it is linked in the usual fashion. The only difference between using an -l option and specifying a file name is that -l surrounds library with ‘lib’ and ‘.a’ and searches several directories.

大致翻译如下:

(1)the linker searches and processes libraries and object files in the order they are specified. Thus, ‘foo.o -lz bar.o’ searches library ‘z’ after file foo.o but before bar.o. If bar.o refers to functions in ‘z’, those functions may not be loaded

链接器按照指定的顺序搜索、处理库和目标文件。因此,当 gcc 处理诸如 “foo.o -lz bar.o” 这样的选项时,会在文件 foo.o 之后但在 bar.o 之前搜索库 “z” 。如果 bar.o 有引用库 “z” 中的函数,则这些函数可能不会被加载(导致出现undefined reference to)

(2)Normally the files found this way are library files—archive files whose members are object files. The linker handles an archive file by scanning through it for members which define symbols that have so far been referenced but not defined. But if the file that is found is an ordinary object file, it is linked in the usual fashion.

库文件由目标文件组成(.o文件:即可重定向文件)。链接器通过扫描库文件来查找目标文件,找出目标文件中已定义的,到目前为止已被引用但尚未定义的符号。找到之后,则以普通的链接目标文件的方式将这个目标文件链接到程序中(注意:并不是链接整个库文件)

实战

现有几个 .c 文件:

a.c

char func_a()
{
        return 'a';
}

char func_aa()
{
        return 'a';
}

b.c

char func_b()
{
        return 'b';
}

x.c

extern char func_b();
extern char func_aa();

char func_x()
{
        return func_b();
}

char func_xx()
{
        return func_aa();
}

main.c

extern char func_a();

int main()
{
        func_a();
        return 0;
}

我们需要将 a.c 和 b.c 打包成 libab.a,将 x.c 编译成 x.o,将 main.c 编译成 main.o。最后再将它们链接在一起,编译成可执行文件

zdzeng@DESKTOP-P5TAD9S:/mnt/e/Test/linker$ gcc -c a.c b.c x.c main.c
zdzeng@DESKTOP-P5TAD9S:/mnt/e/Test/linker$ ar -r libab.a a.o b.o
zdzeng@DESKTOP-P5TAD9S:/mnt/e/Test/linker$ ls
a.c  a.o  b.c  b.o  libab.a  libx.a  main  main.c  main.o  x.c  x.o
zdzeng@DESKTOP-P5TAD9S:/mnt/e/Test/linker$ ar -t libab.a
a.o
b.o

当我们用如下方式链接时可以看到,当 gcc 在链接 x.o 时报错 func_x 中调用的 func_b 找不到。但是 func_xx 中的func_aa 却没有报错

zdzeng@DESKTOP-P5TAD9S:/mnt/e/Test/linker$ gcc main.o -lab -L. x.o -o main
x.o: In function `func_x':
x.c:(.text+0xa): undefined reference to `func_b'
collect2: error: ld returned 1 exit status

将 x.o 的和 libab.a 的编译顺序调换一下就可以解决这个问题

zdzeng@DESKTOP-P5TAD9S:/mnt/e/Test/linker$ gcc main.o x.o -lab -L. -o main
zdzeng@DESKTOP-P5TAD9S:/mnt/e/Test/linker$ ls
a.c  a.o  b.c  b.o  libab.a  libx.a  main  main.c  main.o  x.c  x.o

# 看,这样就将 main 成功编译出来了

但是第一次编译的时候 libab.a 明明也一起参与编译了啊,为什么会提示未找到呢?我们可以看看只编译 main.o 和 libab.a 时可执行文件中的符号表究竟是怎样的,看完之后就知道为什么了

zdzeng@DESKTOP-P5TAD9S:/mnt/e/Test/linker$ gcc main.o -lab -L. -o main
zdzeng@DESKTOP-P5TAD9S:/mnt/e/Test/linker$ readelf -s main

Symbol table '.symtab' contains 64 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
    ......
    33: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS main.c
    34: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS a.c
    49: 000000000000061a    11 FUNC    GLOBAL DEFAULT   13 func_aa
    58: 00000000000005fa    21 FUNC    GLOBAL DEFAULT   13 main
    59: 000000000000060f    11 FUNC    GLOBAL DEFAULT   13 func_a
    ......

从符号表中可以看到,只编译 main.o 和 libab.a 时,并没有看到 b.c 和 func_b,反而看到了main.o 中没有调用的 func_aa。那是因为 main.o 只调用了 func_a,链接器在链接 libab.a 时发现 func_a 函数在 a.o 找到了,就将 a.o 链接进来了(a.o 包含了 main.o 没有调用到的 func_aa)。但是 main.o 并没有调用 b.o 的任何函数,所以链接器在链接 libab.a 时,并没与将其中的 b.o 链接到可执行程序

总结

通过上面的例子再结合 gcc 官方文档给出的说明,我想大家应该已经知道为什么调换链接顺序可以解决 “undefined reference to” 的问题了吧。gcc 按照顺序链接 main.o libab.a x.o,链接到 libab.a 时 gcc 发现 b.o 没有任何一个地方有调用到,所以抛弃了它。导致链接到 x.o 时找不到 b.o 中的实现。最后链接器直接抛出异常提示 “undefined reference to”

问题

为什么 libc.a 库中要将每一个函数都编译成一个 .o 呢?例如 printf.o sscanf.o 等等,都是一个函数一个 .o 文件

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值