静态链接,动态链接,静态库,共享库这些概念的详解

静态链接,动态链接,静态库,共享库,这些名词的资料在网上一大堆,但是我看了这些资料,总觉得有些地方怪怪的,有些文章描述某个概念特别好,但是在描述其他的概念的时候就有些凌乱。于是,我把这些资料给整理一下,厘清这些名词的真正含义。

本文一开始将各个概念全部罗列一遍(一开始的时候不讲各个之间的关系,或者比较少讲),然后才将各个概念联系起来(这就是外面那些资料缺少的地方,那些资料都没有讲清楚)

编译过程

要了解这些概念,编译过程是前提条件。
编译过程分成下面几个过程:预处理–>汇编–>编译–>链接。这些概念大家也可以到网上去搜索,一大堆。
本文中的这些概念就是发生在最后一个环节:链接。千万要记住这一点,很重要。

静态链接

关于静态链接和动态链接,网上这篇文章写得通俗易懂:静态链接和动态链接的区别,这里对这篇文章进行了摘抄:
1. 什么是静态链接:主函数中调用了库中的某个函数(one()),并且把该函数编码进了可执行文件中。见下图:

2. 静态链接存在的第一个问题:若需要调用库中的函数过多,会造成可执行文件体积巨大。见下图:

3. 静态链接存在的第二个问题:因为库中的函数代码嵌入了可执行程序,所以如果库更新了,想要更新可执行文件中库部分的代码,就只能重新编译。见下图:

动态链接

为了解决静态链接的以上2个问题,可以让库中的函数不要直接编译进可执行程序,而是放在内存中,可执行程序在使用(one)函数时,直接读内存的地址就可以用了。这样既减小了可执行程序的大小,又可以方便的使用库函数更新。这种方式即为——动态链接。见下图:

动态链接的优点:
1. 执行文件很小。
2. 当依赖的库函数发生升级了,执行文件不用重新编译。
动态链接的缺点:
一个可执行应用,在A服务器上进行了编译–>链接操作,在链接的时候所依赖的那些函数库是存在A服务器上的。当把这个可执行应用拿到B服务器上去运行,因为在B服务器上该可执行应用所依赖的函数库可能缺少或者位置不正确,而导致报错。所以,在使用这个可执行应用之前,需要在B服务器上进行make一下生成所对应的可执行应用才可以。这也就是 configure–>make–>make install的真正含义所在。

静态库和共享库(动态库,动态链接库)概述

  1. 静态库,在windows的后缀为 .lib(library),在linux的后缀为 .a(archive)
  2. 共享库,又称为动态库,又称为动态链接库,(这3个叫法在网上到处混着乱飞,就是指同一个),在windows的后缀为 .dll(dynamic link library),在linux的后缀为 .so(shared object)

在讲下面之前,借用《C编程一站式学习》中的“链接详解”这个章节中的例子来举例说明,顺便说一下《C编程一站式学习》这本书写得不错,但是“链接详解”这个章节还是没有说清楚,反而越说越糊涂。

/* stack.c */
char stack[512];
int top = -1;
/* push.c */
#include <stdio.h>

extern char stack[512];
extern int top;
void push(char c)
{
    printf("....");
    stack[++top] = c;
}
/* pop.c */
extern char stack[512];
extern int top;
char pop(void)
{
return stack[top--];
}
/* is_empty.c */
extern int top;
int is_empty(void)
{
return top == -1;
}
/* stack.h */
#ifndef STACK_H
#define STACK_H
extern void push(char);
extern char pop(void);
extern int is_empty(void);
#endif
/* stack.h */
#ifndef STACK_H
#define STACK_H
extern void push(char);
extern char pop(void);
extern int is_empty(void);
#endif
/* main.c */
#include <stdio.h>
#include "stack.h"
int main(void)
{
push('a');
return 0;
}

上面这个例子也是很浅显易懂的。

静态库

gcc -c stack.c push.c pop.c is_empty.c
//上面这个步骤是compile,还不涉及link,结果生成了各自的.o,通过反汇编能看到,凡是使用printf()这个函数的地方都用了 0X0 这样的占位符先占着位置(printf()是出自libc.so这个大名鼎鼎的共享库),凡是彼此之间相互用到的也都用了0X0 这样的占位符先占着位置。
ar cr libstack.a stack.o push.o pop.o is_empty.o //创建libstack.a这个静态库

ar 这个命令仅仅是压缩,什么都不涉及,不会对那些.o有任何修改,记住就是压缩!既然是压缩,那么原来怎么样就是怎么样了。
通过上面ar生成的 libstack.a 就是静态库,那为什么要取这样的名字 lib + stack + .a,这个是规范,要给下面讲到的链接所用,死记即可。
静态库的特点如下:
1,仅仅是.o的打包而已
2,.o中假如是 .c之间相互用到的,那么是0X0占位符
3,.o中假如有来自共享库(见下面,这里暂时不管)的,那么也是0X0占位符

静态库和静态链接、动态链接的结合

这个部分就是外面网上好多没有提到的,这样就导致了看到后面一头雾水,就是压根不明白到底什么是什么。

默认“动态链接”

gcc main.c -L. -lstack -Istack -o main  #这是一步compile和link合在一起的步骤

-L:大写的L,表示静态库的文件夹(看清每个字,理解每个字,是文件夹,而且是静态库)
-l:小写的l,表示链接的时候要链接 libstack.so或者libstack.a 这个文件,具体找到共享库(.so)还是静态库(.a),libstack.so先找到就用这个共享库,找不到则在-L指定的文件夹下面找libstack.a这个静态库

那是不是找到.so动态库就是动态链接?找到.a静态库就是静态链接?
否,否,否!!!
千万不要被他们之间摸棱两可的命名给混淆了,接着看:

gcc的链接,默认的情况下采用“动态链接”,比如上面的那条命令。那么按照“动态链接”的概念来进行链接,main.o当链接到“静态库”的时候,假如只是单纯的把main.o中的push()替换成“静态库”中的push()函数地址,因为libstack.a是不可能更改的,里面的那些彼此之间的函数调用以及调用的printf()仅仅是0X0地址,那肯定不对的。所以,尽管是“动态链接”,但是遇到libstack.a这个静态库的时候,只有华山一条路,只能采用“静态链接”的方式,将所调用的push()函数给拷贝到应用程序main中,同时调整拷贝过来的push()中所用到的libc.so中的printf()函数地址为间接地址(也就是说printf()还是“动态链接”的)。这个过程想想都能明白的。

总结一下:
“动态链接”,当链接到“静态库”的时候,采用“静态链接”,全部拷贝过来。但是对于main自己本身所用到的“动态库”和“静态库”中所用到的“动态库”则采用“动态链接”。

强制使用“静态链接”

gcc -static main.c -L. -lstack -Istack -o main

-static,表示使用“静态链接”的方式。那么不管是遇到“静态库”还是“动态库”,还是“静态库”中用到“动态库”,将一律拷贝到应用程序中,更改调用地址,也就是说一律采用“静态链接”的方式。

总结一下:
“静态链接”,不管遇到“静态库”还是“动态库”,统统采用“静态链接”的方式。

共享库(动态库,动态链接库)

gcc -c -fPIC stack/stack.c stack/push.c stack/pop.c stack/is_empty.c
//compile成.o,通过反汇编可以看到调用的函数地址为0x0(%ebx),本质上还是仅仅占位符,可以看成0X0
gcc -shared -o libstack.so stack.o push.o pop.o is_empty.o
//将上面生成的.o编译成共享库libstack.so,通过反汇编可以看到上面的0x0已经被替换成间接地址了。

到这里为止,可以看到“共享库”和“静态库”有本质差别的,差别就在于调用函数的地址上,“共享库”提供了一个简介地址可以在后面的链接中以及运行中找到调用函数。

共享库和静态链接、动态链接的结合

还是和上面一样,“共享库”和“静态链接”、“动态链接”没有本质上的瓜葛。

gcc main.c -g -L. -lstack -Istack -o main
./main
./main: error while loading shared libraries: libstack.so: cannot open shared object file: No such file or directory

从前面讲到的知道,这是“动态链接”,而且链接的是“动态库”libstack.so,而且还要链接printf()的动态库libc.so。那么最终生成的main这个可执行程序中所涉及的函数全部都是间接寻址。

当执行./main的时候报错,说libstack.so找不到,这个时候要记得:.L是指的“静态库”的文件夹,而对于“动态库”的寻找,一般是在/usr/lib下面,所以我们还需要将生成的libstack.so放在/usr/lib下面,这也就是 make install 的作用,将生成的“动态库”放在 /usr/lib下面。

假如我们还是想用“静态链接”来链接,那么就改用下面的来执行:

gcc -static main.c -L. -lstack -Istack -o main

这样,尽管都是“动态库”,但是还是强制使用了“静态链接”。

全部总结一下:
当“动态链接”遇到“静态库”,则采用“静态链接”。
当“动态链接”遇到“动态库”,则采用“动态链接”。
当“静态链接”遇到“静态库”、“动态库”,一律采用“静态链接”。

本文结束,希望你能彻底搞清这些名词之间的关系,“静态链接”和“静态库”,都有“静态”2个字,其实是2个方面,2者严格意义上完全不一样。“动态链接”和“动态链接库”,更加具有混淆性,也是2个方面,千万别被字面上的含义弄混淆。

顺便说一下:ldd和nm是2个用来分析链接的常用工具。

参考资料

C编程一站式学习

发表评论

邮箱地址不会被公开。 必填项已用*标注