遇到一个问题: 封装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.oar -r libcal.a addLib.o subLib.o gcc main.c -L./ -lcaladdLib.o : addLib.cgcc -c $<subLib.o : subLib.cgcc -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文件的某个符号又要被动态库使用,这就出现了上面的报错了。解决办法无非就是让可执行程序去调用一下该符号了。

链接与自定义函数名同名的库函数相关推荐

  1. 对象名和函数名同名引起的莫名错误

    <html> <head><script type="text/javascript"> var Validator = function() ...

  2. python自定义函数名_使用自定义名称创建Python动态函数

    如果这个问题已经提出并得到了回答,我深表歉意. 我需要做的是非常简单的概念,但不幸的是,我还没有找到一个在线答案. 我需要在Python(Python2.7)中使用运行时的自定义名称创建动态函数.每个 ...

  3. error C3861: “XXXX(自定义函数名)”: 找不到标识符

    问题描述 调试程序时,输出窗口提示找不到标识符错误! 错误原因 程序中没有对自定义函数进行声明 解决方案 在主函数前对自定义函数进行声明,问题解决!

  4. C语言 函数 (库函数 · 自定义函数 · 函数参数 · 函数调用 · 嵌套调用链式访问 · 递归)

    文章目录: 一.函数是什么? 二.库函数 2.1 为什么要有库函数? 2.2 如何学习库函数? 2.3 我们就以 strcpy( ) 函数,来参照文档自学一下: 2.4 总结: 三.自定义函数 3.1 ...

  5. java调mongodb自定义函数,自定义UDF函数,从hive保存到mongodb

    (可以通过idea工具调试UDF函数,第二步中会提供参考) 一.自定义UDF函数: 1.首先是pom.xml文件 xmlns:xsi="http://www.w3.org/2001/XMLS ...

  6. LTspice基础教程-027.自定义函数;func指令用法

    在LTspice中,我们可以自定义函数.语法如下: .func <name>([args]) {<expression>} func是function的缩写:name是自定义函 ...

  7. MySQL自定义函数(CREATE FUNCTION)

    在使用 MySQL 的过程中,MySQL 自带的函数可能完成不了我们的业务需求,这时候就需要自定义函数. 自定义函数是一种与存储过程十分相似的过程式数据库对象.它与存储过程一样,都是由 SQL 语句和 ...

  8. mysql自定义函数的分号_MySQL 第八篇:自定义函数、存储过程、游标-阿里云开发者社区...

    我把MySQL的内容整理成9篇博客,学完这9篇博客虽不能说能成为大神,但是应付一般中小企业的开发已经足够了,有疑问或建议的欢迎留言讨论. 自定义函数 一.函数的概念与定义 1.理解函数 函数可以看作是 ...

  9. python学习笔记之自定义函数

    live long and prosper 自定义函数 def greet_user():"""现实简单的问候语"""print(" ...

  10. mysql自定义函数示例代码,以及属性介绍

    以下是一个MySQL自定义函数的示例代码,该函数实现了将指定字符串中的大写字母转换为小写字母的功能: DELIMITER $$ CREATE FUNCTION to_lower(str VARCHAR ...

最新文章

  1. Spring Boot整合Spring Data JPA操作数据
  2. 用户请求队列化_分布式消息队列选型分析
  3. cocos2d-x 帧动画学习
  4. python控制语句中的条件语句_『Python』条件控制语句
  5. 多线程与高并发(五):强软弱虚四种引用以及ThreadLocal的原理与源码
  6. bootstrap获取弹框数据_Bootstrap模态弹出框的实例教程
  7. 可燃气体浓度多少合格_安燃无恙 | 可燃气体报警器的常见故障处理
  8. Windows Server 版本信息及支持期 Win10系统各版本服务起止日期。
  9. ssh整合步骤之二(架构设计)
  10. vue权限问题解决方案
  11. MySQL查询语句(select)详解(1)
  12. 电商自营藏猫腻 苏宁国美京东的套路谁最深?
  13. 计算机思维导论raptor实验报告,计算机基础实验报告Raptor.pdf
  14. 联想用u盘重装系统步骤_联想笔记本u盘重装系统,详细教您联想笔记本怎么使用u盘重装系统...
  15. Vue3源码阅读(八)effect
  16. 一次CSDN客户体验经历
  17. Poto Editor for Mac(mac照片编辑器)
  18. 幻灯片放映时无法切换到下一张
  19. 1046: 数值统计
  20. 【苹果推iMessage】软件安装通过ApplseScript节制iMessage客户端

热门文章

  1. No filesystem could mount root, tried: ext3 ext2 ext4 vfat msdos
  2. 公众号菜单栏如何添加设置一键导航?
  3. 项目复盘内容及注意事项
  4. 如何升级到 macOS Mojave
  5. MacOS Mojave的安装
  6. 职场知识:什么是软件程序员?它是做什么的?
  7. 解析出错,标准Json格式数据
  8. 笔记本可以连wifi,但很卡或者无法访问互联网的一种解决方法
  9. watch和computer的区别
  10. VB6+Mo图层顺序的调整(MoveToTop、MoveTo、MoveToBottom )