Prayer

在一般中寻求卓越
posts - 1246, comments - 190, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

遇到一个问题: 封装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;   ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

将其封装成静态库.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; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
//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); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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

#include <stdio.h> #include "addLib.h" #include "subLib.h"  int main(void) {     add(4, 6);     return 0; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

简单写个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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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

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

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

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

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

gcc main.c -L./ -lcal
  • 1

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

gcc main.o -L./ -lcal
  • 1

链接器从左向右扫描链接命令行参数中的.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; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

int main(void) {     add(4, 6);     sub(9, 2);      return 0; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

再编译就出现重定义错误了: 
这里写图片描述 
原因也很简单,因为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文件的某个符号又要被动态库使用,这就出现了上面的报错了。解决办法无非就是让可执行程序去调用一下该符号了。


只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理