链接与自定义函数名同名的库函数

遇到一个问题: 封装SQLite3成静态库,过程中发现SQLite3的源码的shell.c中有main函数:

int SQLITE_CDECL main(int argc, char **argv){
  char *zErrMsg = 0;
  ShellState data;
  const char *zInitFile = 0;
  int i;
  int rc = 0;
  int warnInmemoryDb = 0;
  int readStdin = 1;
  int nCmd = 0;
  char **azCmd = 0;
  ...

将其封装成静态库.a文件自然是被使用者调用的,也就是说使用者也要有自己的main函数才行。如此说来,在使用该.a的项目中就有了两个main函数,那应该是一定编译不过的,然而事实并非如此,程序能正常编译且符合设计逻辑运行,这涉及gcc中链接器ld的链接过程,于是通过自行编写测试程序试验一番。

新建如下文件:
这里写图片描述

addLib.和subLib.将编译为库函数使用,其实现为:

//addLib.h
#ifndef __ADDLIB_H__
#define __ADDLIB_H__

int add(int a, int b);

#endif /* __ADDLIB_H__ */

//addLib.c
#include <stdio.h>
#include "addLib.h"

int add(int a, int b)
{
    printf("%d + %d = %d\n", a, b, a + b);
    return 0;
}
//subLib.h
#ifndef __SUBLIB_H__
#define __SUBLIB_H__

#include <stdio.h>
void sub(int a, int b);

#endif /* __SUBLIB_H__ */

//subLib.c
#include "subLib.h"

void sub(int a, int b)
{
    printf("%d - %d = %d\n", a, b, a - b);
}

而main.c是对该库函数的调用:

#include <stdio.h>
#include "addLib.h"
#include "subLib.h"

int main(void)
{
    add(4, 6);
    return 0;
}

简单写个makefile:

all : addLib.o subLib.o
    ar -r libcal.a addLib.o subLib.o 
    gcc main.c -L./ -lcal

addLib.o : addLib.c
    gcc -c $<

subLib.o : subLib.c
    gcc -c $<

clean:
    rm *.o *.a -rf

编译通过:
这里写图片描述

运行正确:
这里写图片描述

Linux的nm可列出目标文件的符号清单,通过它查看libcal.a:
这里写图片描述

图中的T表示该符号位于代码段,U表示在当前文件是未定义的,即该符号是定义在别的文件。

在这个例子中,链接的命令为

gcc main.c -L./ -lcal

其中gcc main.c其实是做了编译和链接,所以最后一步的链接操作是

gcc main.o -L./ -lcal

链接器从左向右扫描链接命令行参数中的.o和.a,最终目的是确定“最终.o文件集合”和各个.o文件中的外部符号的定义位置。
以本程序为例,
(1)首先是扫描main.o,main.o会无条件被加入到“最终.o文件集合”中,该文件引用了main符号,它是程序开始执行的符号,会将main放入“已定义符号表”中,接着又引用了外部符号符号add,因此会将add放入“未定义符号表”中。
(2)扫描到libcal.a中addLib.o,“未定义符号表”中存放的add在addLib.o中找到了定义,于是将addLib.o文件加入到“最终.o文件集合”中,且将add符号从“未定义符号表”中转换到“已定义符号表”中。但是在扫描addLib.o中,发现了外部符号printf,于是printf符号被放入“未定义符号表”。
(3)扫描到libcal.a中的subLib.o,此时“未定义符号表”中存放的是printf,并不能在subLib.o中找到定义,直接略过该文件,所以subLib.o并不加入到“最终.o文件集合”中,其中的符号信息也没有被加载“未定义符号表”和“已定义符号表”。
(4)“未定义符号表”里仍然存在printf符号,所以链接器会继续扫描,它往哪里扫描?链接命令上写到-lcal就截止了,其实c程序默认会去链接标准c库的,找到标准c库的定义printf符号的.o文件,并把该文件加入“最终.o文件集合”中,链接操作至此完成。注意链接完成的标志是“未定义符号表”中为空,也就是不能出现未定义的符号。

如上分析,因为main.c程序中并没有调用sub函数,subLib.o并不会被加入到“最终.o文件集合,那么在subLib.c中加上如下代码且不调用,同样是能编译通过咯:

void sub(int a, int b)
{
    printf("%d - %d = %d\n", a, b, a - b);
}

int main(void)
{
    printf("in lib mian!\n");
    return 0;
}

果然如此:
这里写图片描述
main.c的main符号在库函数外部,链接器已经认识这个符号了,会将其放入“已定义的符号表”中,所以不会去扫描cal库内的subLib.o里的main符号。
再做改动,在main.c的main函数中调用subLib.c的sub函数:

int main(void)
{
    add(4, 6);
    sub(9, 2);

    return 0;
}

再编译就出现重定义错误了:
这里写图片描述
原因也很简单,因为main函数调用了sub函数导致subLib.c中的sub符号一开始放入到“未定义符号集”,再找到subLib.o后,该符号会被放入到“已定义符号集”,subLib.o也会随之加入“最终.o文件集合”中,问题就出现了:该集合中main.o和subLib.o均包含了main符号,自然就报错了!

综上所述,我们可以推论,链接器对目标文件(.o)和库文件(.a)是区别对待的。我们知道,可执行程序是由一系列的.o文件“合并”而成,以静态链接为例,“最终.o文件集合”中除了包含我们显示提供的由.c编译而来.o文件外,还有从.a库文件提取出来的.o文件,可执行程序对由.c编译而成的.o文件无条件的包含到“最终.o文件集合”中,而对从.a库提取的.o并非全盘提取,而是“按需”提取,“按需”是根据“未定义符号表”中的符号去提取的。这也符合软件设计的思想,尽可能使得可执行文件的size小。

实际开发中,我还遇到这样一个问题: 可执行程序链接了n个静态库和一个动态库,动态库想要调用静态库里的某个函数,运行时报错找不到该符号,通过前面的学习,可以分析原因就是在于,可执行程序虽然链接了这个静态库但并没有使用静态库的某个.o文件,导致.o没有真正被链接到可执行程序,而该.o文件的某个符号又要被动态库使用,这就出现了上面的报错了。解决办法无非就是让可执行程序去调用一下该符号了。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值