浅析静态库链接原理

标签: 静态库 链接 原理 | 发表时间:2014-09-15 22:47 | 作者:Kevin Lynx
出处:http://www.cppblog.com/

静态库的链接基本上同链接目标文件 .obj/.o相同,但也有些不同的地方。本文简要描述linux下静态库在链接过程中的一些细节。

静态库文件格式

静态库远远不同于动态库,不涉及到符号重定位之类的问题。静态库本质上只是将一堆目标文件进行打包而已。静态库没有标准,不同的linux下都会有些细微的差别。大致的格式 wiki上描述的较清楚:

   Global header
-----------------        +-------------------------------
File header 1       ---> | File name
File content 1  |        | File modification timestamp 
-----------------        | Owner ID
File header 2            | Group ID
File content 2           | File mode
-----------------        | File size in bytes
...                      | File magic
                         +-------------------------------

File header很多字段都是以ASCII码表示,所以可以用文本编辑器打开。

静态库本质上就是使用 ar命令打包一堆 .o文件。我们甚至可以用 ar随意打包一些文件:

   $ echo 'hello' > a.txt && echo 'world' > b.txt
$ ar -r test.a a.txt b.txt
$ cat test.a
!<arch>
a.txt/          1410628755  60833 100   100644  6         `
hello
b.txt/          1410628755  60833 100   100644  6         `
world

链接过程

链接器在链接静态库时,同链接一般的 .o基本相似。链接过程大致可以归纳下图:

总结为:

  • 所有传入链接器的 .o都会被链接进最终的可执行程序;链接 .o时,会将 .o中的 global symbolunresolved symbol放入一个临时表
  • 如果多个 .o定义了相同的 global symbol,那么就会得到多重定义的链接错误
  • 如果链接结束了, unresolved symbol表不为空,那么就会得到符号未定义的链接错误
  • .a静态库处理本质上就是处理其中的每一个 .o,不同的是,如果某个 .o中没有一个符号属于 unresolved symbol表,也就是链接器此时怀疑该 .o没有必要,那么其就会被忽略

可以通过一些代码来展示以上过程。在开发C++程序时,可以利用文件静态变量会先于 main之前执行做一些可能利于程序结构的事情。如果某个 .o(包含静态库中打包的 .o)被链接进程序,那么其文件静态变量就会先于 main初始化。

    // test.cpp
#include <stdio.h>

class Test {
public:
    Test() {
        printf("Test ctor\n");
    }
};

static Test s_test;

// lib.cpp
#include <stdio.h>

class Lib {
public:
    Lib() {
        printf("Lib ctor\n");
    }
};

static Lib s_lib;

// main.cpp
#include <stdio.h>

int main() {
    printf("main\n");
    return 0;
}

以上代码 main.cpp中未引用任何 test.cpp``lib.cpp中的符号:

   $ g++ -o test test.o lib.o main.o
$ ./test
Lib ctor
Test ctor
main

生成的可执行程序执行如预期,其链接了 test.o``lib.o。但是如果把 lib.o以静态库的形式进行链接,情况就不一样了:为了做对比,基于以上的代码再加一个文件,及修改 main.cpp

    // libfn.cpp
int sum(int a, int b) {
    return a + b;
}

// main.cpp
#include <stdio.h>

int main() {
    printf("main\n");
    extern int sum(int, int);
    printf("sum: %d\n", sum(2, 3));
    return 0;
}

libfn.olib.o创建为静态库:

   $ ar -r libfn.a libfn.o lib.o
$ g++ -o test main.o test.o -lfn -L.
$ ./test
Test ctor
main
sum: 5

因为 lib.o没有被链接,导致其文件静态变量也未得到初始化。

调整链接顺序,可以进一步检验前面的链接过程:

   # 将libfn.a的链接放在main.o前面

$ g++ -o test test.o -lfn main.o  -L.
main.o: In function `main':
main.cpp:(.text+0x19): undefined reference to `sum(int, int)'
collect2: ld returned 1 exit status

这个问题遇到得比较多,也有点让人觉得莫名其妙。其原因就在于链接器在链接 libfn.a的时候,发现 libfn.o依然没有 被之前链接的 *.o引用到,也就是没有任何符号在 unresolved symbol table,所以 libfn.o也被忽略。

一些实践

在实际开发中还会遇到一些静态库相关的问题。

链接顺序问题

前面的例子已经展示了这个问题。 调整库的链接顺序可以解决大部分问题,但当静态库之间存在环形依赖时,则无法通过调整顺序来解决。

-whole-archive

-whole-archive选项告诉链接器把静态库中的所有 .o都进行链接,针对以上例子:

   $ g++ -o test -L. test.o -Wl,--whole-archive -lfn main.o -Wl,--no-whole-archive
$ ./test
Lib ctor
Test ctor
main
sum: 5

lib.o也被链接了进来。 -Wl选项告诉gcc将其作为链接器参数传入;之所以在命令行结尾加上 --no-whole-archive是为了告诉编译器不要链接gcc默认的库

可以看出这个方法还是有点暴力了。

–start-group

格式为:

   --start-group archives --end-group

位于 --start-group --end-group中的所有静态库将被反复搜索,而不是默认的只搜索一次,直到不再有新的 unresolved symbol产生为止。也就是说,出现在这里的 .o如果发现有 unresolved symbol,则可能回到之前的静态库中继续搜索。

   $ g++ -o test -L. test.o -Wl,--start-group -lfn main.o -Wl,--end-group
$ ./test
Test ctor
main
sum: 5

查看 ldd关于该参数的man page还可以一窥链接过程的细节:

The specified archives are searched repeatedly until no new undefined references are created. Normally, an archive is searched only once in the order that it is specified on the command line. If a symbol in that archive is needed to resolve an undefined symbol referred to by an object in an archive that appears later on the command line, the linker would not be able to resolve that reference. By grouping the archives, they all be searched repeatedly until all possible references are resolved.

嵌套静态库

由于 ar创建静态库时本质上只是对文件进行打包,所以甚至可以创建一个嵌套的静态库,从而测试链接器是否会递归处理静态库中的 .o

   $ ar -r libfn.a libfn.o
$ ar -r liboutfn.a libfn.a lib.o
$ g++ -o test -L. test.o main.o -loutfn
main.o: In function `main':
main.cpp:(.text+0x19): undefined reference to `sum(int, int)'
collect2: ld returned 1 exit status

可见链接器并不会递归处理静态库中的文件

之所以要提到嵌套静态库这个问题,是因为我发现很多时候我们喜欢为一个静态库工程链接其他静态库。当然,这里的链接并非真正的链接(仅是打包),这个过程当然可以聪明到将其他静态库里的 .o提取出来然后打包到新的静态库。

如果我们使用的是类似 scons这种封装更高的依赖项管理工具,那么它是否会这样干呢?

基于之前的例子,我们使用scons来创建 liboutfn.a

   # Sconstruct
StaticLibrary('liboutfn.a', ['libfn.a', 'lib.o'])

使用文本编辑器打开 liboutfn.a就可以看到其内容,或者使用:

   $ ar -tv liboutfn.a
rw-r--r-- 60833/100   1474 Sep 14 02:59 2014 libfn.a
rw-r--r-- 60833/100   2448 Sep 14 02:16 2014 lib.o

可见scons也只是单纯地打包。 所以,在scons中构建一个静态库时,再 链接其他静态库是没有意义的

参考文档

原文地址: http://codemacro.com/2014/09/15/inside-static-library/
written by Kevin Lynx  posted at http://codemacro.com



Kevin Lynx 2014-09-15 22:47 发表评论

相关 [静态库 链接 原理] 推荐:

浅析静态库链接原理

- - C++博客-首页原创精华区
静态库的链接基本上同链接目标文件 .obj/.o相同,但也有些不同的地方. 本文简要描述linux下静态库在链接过程中的一些细节. 静态库远远不同于动态库,不涉及到符号重定位之类的问题. 静态库本质上只是将一堆目标文件进行打包而已. 静态库没有标准,不同的linux下都会有些细微的差别. 大致的格式 wiki上描述的较清楚:.

静态库与动态库的创建与使用 - Jackon Yang

- - 博客园_首页
静态库:链接时,库与编译生成的 .o 文件一起打包到可执行文件中. linux / windows 下扩展名分别为:.a / .lib. 动态库:链接时,不会打包到可执行文件中, 执行时会动态的加载动态库.  linux / windows 下扩展名分别为:.so / .dll. 合入动态库的更新,只要替换库即可.

gcc创建和使用动态库、静态库

- - CSDN博客综合推荐文章
本文以工程 libtest为例来说明用gcc创建和使用静态库、动态库的过程. libtest工程目录结构如下:. libtest/include/hello.h文件内容:. libtest/lib/hello.c文件内容:. libtest/src/main.c文件内容:. 1,进入libtest/lib目录,执行命令:.

技巧:Linux 动态库与静态库制作及使用详解

- mettli - IBM developerWorks 中国 : Linux : Articles,Tutorials
Linux 应用开发通常要考虑三个问题,即:1)在 Linux 应用程序开发过程中遇到过标准库链接在不同 Linux 版本下不兼容的问题; 2)在 Linux 静态库的制作过程中发现有别于 Windows 下静态库的制作方法;3)在 Linux 应用程序链接第三方库或者其他静态库的时候发现链接顺序的烦人问题.

HTTP长链接和短链接

- - Web前端 - ITeye博客
HTTP协议与TCP/IP协议的关系. HTTP的长连接和短连接本质上是TCP长连接和短连接. HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议. IP协议主要解决网络路由和寻址问题,TCP协议主要解决如何在IP层之上可靠的传递数据包,使在网络上的另一端收到发端发出的所有包,并且顺序与发出顺序一致.

链接大放送

- Fenix - 增强视觉 | 计算机视觉 增强现实
google收购了PittPatt公司. 该公司主要提供人脸检测,识别,跟踪等技术. Picasa里面不是已经有相当强大的人脸识别了么. 这样算来google已经收购了三家CV的创业公司. Google talk中开始采用SRI Technology的SRI 2D视频稳定技术. SRI是一家独立的非盈利的研发机构,鄙人是第一次听说,官网介绍如下:.

911全链接(1)

- Beardnan - 1416 教室
这几天的假期彻底被911打败了. 看了媒体眼花缭乱的911报道,心里只有一个念头,您这么High,下一次怎么弄. 《纽约时报》的网络特刊The Reckoning,分成9个单元,每个单元内容都很丰富,有深度报道也有邀请读者参与的互动;视觉部分则有专题摄影,视频和互动图表相配合. 整个专题的页面清秀,主图是当年911之后的公民摄影展览Here Is New York: A Democracy of Photographs中最为卖座的照片,摄影师Katie Day Weisberger在911事件之前几个月在飞机上拍到了这张照片,当时还是个刚拿相机不久的学生.

Office常用资源链接

- Weiye - ExcelFans
下面是我经常访问的一些站点以及对这些站点的简要介绍,他们都非常优秀,是我重要的学习资源. 国内优秀的Excel专业站点,有着海量的贴子、文章和应用资源,论坛上活跃着大量的MS MVP和Excel技术高手. 站点内容和活动丰富多彩,近几年出版的一系列Excel图书广受读者好评. 著名的Excel博客站点,有许多Excel技术、技巧和应用方面的文章及新鲜资讯,大多都是博主研究和使用Excel的成果,经常有许多Excel界的大师访问并留言或发表文章.

链接器做什么

- - Dutor
  前几天,在组内分享了关于链接器的一些东西,在这里总结一下. 讨论的背景主要是基于C/C++,Linux平台相关.   学习或者了解链接器,有一些基本的问题需要关心:链接器做些什么;链接器和体系结构;程序是怎样生成的.   链接器之所以存在或者产生,基本上是由于程序开发的模块化. 这里讲的模块,主要是编译概念上的模块,通常他们按照功能划分,比如一个.c或者.cpp文件就是一个编译单元,就是一个模块,编译后就产生一个.o目标文件.

android中的自动链接

- - CSDN博客推荐文章
    下面和大家分享一下android中的自动链接,也就是android系统会自动识别我们文本中的网址,电话号码,邮箱地址,当我们点击的时候会自动打开浏览器,自动启动拨号程序,自动打开发送邮件的程序. 这么神奇的效果是怎么实现的呢.         //设置自动链接url,邮箱,电话号码. 由于模拟器不自带邮件客户端,所以自动链接邮箱的效果看不到,真机上就可以了.