查看原文
其他

C语言实用讲解:什么是动态链接与静态链接?

嵌入式ARM 2021-01-31

The following article is from 嵌入式大杂烩 Author ZhengN


静态链接与动态链接的实验(Windows)

什么是链接?

对于初学C语言的朋友,可能对链接这个概念有点陌生,这里简单介绍一下。

我们的C代码编译生成可执行程序会经过如下过程:


链接就是把目标文件与一些库文件生成可执行文件的一个过程。关于更详细的编译过程,可查阅往期笔记:【本质】你知道C语言编译的过程吗?

静态、动态链接?

1、什么是静态链接?

静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。

链接器是一个独立程序,将一个或多个库或目标文件(先前由编译器或汇编器生成)链接到一块生成可执行程序。

这里的库指的是静态链接库,Windows下以.lib为后缀,Linux下以.a为后缀。

2、什么是动态链接?

动态链接(Dynamic Linking),把链接这个过程推迟到了运行时再进行,在可执行文件装载时或运行时,由操作系统的装载程序加载库。

这里的库指的是动态链接库,Windows下以.dll为后缀,Linux下以.so为后缀。

值得一提的是,在Windows下的动态链接也可以用到.lib为后缀的文件,但这里的.lib文件叫做导入库,是由.dll文件生成的。

3、静态链接与动态链接的优缺点?

(1)静态链接的优缺点:

优点:

  • 代码装载速度快,执行速度略比动态链接库快;
  • 只需保证在开发者的计算机中有正确的.lib文件,在以二进制形式发布程序时不需考虑在用户的计算机上.lib文件是否存在及版本问题。

缺点:

  • 使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费。

(2)动态链接的优缺点:

优点:

  • 生成的可执行文件较静态链接生成的可执行文件小;
  • 适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试;
  • 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;
  • DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;

缺点:

  • 使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息;
  • 速度比静态链接慢;

静态、动态链接实验

下面的实验基于Windows平台。Windows下的静态链接、动态链接实验网上较多的是使用一些IDE如Visual Studio等通过图形界面来操作,这样就会掩盖了很多细节。

本篇笔记我们不使用IDE,而是以Windows平台搭配MinGW来演示,以便于日后我们在Linux下操作时可以比较快地进行切换。

我们先编写如下代码(共三个文件):

文件1(main.c):

#include "test.h"

int main(void)
{
print_hello();
system("pause");
return 0;
}

文件2(test.c):

#include "test.h"

void print_hello(void)
{
printf("hello world\n");
}

文件3(test.h):

#ifndef __TEST_H
#define __TEST_H

#include <stdio.h>
#include <stdlib.h>

void print_hello(void);

#endif

此时我们的代码目录如下:


1、静态链接实验

进入我们的代码路径,输入命令:

gcc -c test.c main.c


编译、汇编指定的源文件(也就是编译源文件),将每一个源文件编译成对应的目标文件。此时文件夹下多出了test.omain.o文件:


接下来使用ar工具把test.omain.o打包成一个静态库文件lib_test.lib,输入命令:

ar rv lib_test.lib test.o main.o


其实,用MinGW可以生成.a后缀和.lib后缀的静态链接库,这里生成的是.lib后缀的静态库。此时文件夹下多出了.lib文件:


然后把这个静态库链接成可执行文件lib_test.exe,输入命令:


此时文件夹下多出了可执行文件lib_test.exe


双击运行:


可见,运行结果与预期一致,说明我们使用静态链接的方式生成的可执行文件没问题。

这个可执行文件的运行并不依赖于lib_test.lib文件,我们可以试着把这个文件删除之后再运行,仍然可以正常运行。


2、动态链接实验

我们把test.c编译成动态库文件dll_test.dll,输入命令:

gcc test.c -shared -o dll_test.dll


此时文件夹下多出了动态库文件dll_test.dll


我们用该动态库文件dll_test.dllmain.c一起编译生成可执行文件dll_test.exe,输入命令:

gcc dll_test.dll main.c -o dll_test.exe


此时文件夹下多出了可执行文件dll_test.exe


双击运行:


可见,运行结果与预期一致,说明我们使用动态链接的方式生成的可执行文件没问题。

这个可执行文件的运行依赖于dll_test.dll文件,我们可以试着把这个文件删除之后再运行。运行出现如下错误:


那是因为使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在。

动态链接的方式使用得很广泛,比如我们电脑系统盘的System32文件夹下就有很多动态链接库文件:


腾讯QQ安装目录下:


上面使用的命令其实与Linux下操作的命令大多都很相似,我们只要明白这么一回事就可以很快地在Linux下进行操作。


静态链接与动态链接的实验(Windows)

前言

上一篇分享了静态链接与动态链接的实验(Windows):什么是动态链接与静态链接?。这一篇分享Linux下的笔记,同时对上一篇笔记做一个补充。

首先,我们把静态链接与动态链接做一个这样子的比喻:把链接过程看做我们平时学习时做笔记的过程。

我们平时学习时准备一本笔记本专门记录我们的学习笔记,比如在某本书的某一页上看到一个很好很有用的知识,这时候我们有两种方法记录在我们的笔记本上。

一种是直接把那一页的内容全部抄写一遍到笔记本上(静态链接);另一种是我们在笔记本上做个简单的记录(动态链接),比如写上:xxx知识点在《xxx》的xxx页。

从这两种方法中我们可以很清楚地知道两种方式的特点。第一种方式的优点就是我们在复习的时候就很方便,不用翻阅其它书籍了,但是缺点也很明显,就是占用笔记本的空间很多,这种方法很快就把我们的笔记本给写满了。

第二种方式的优点就是很省空间,缺点就是每当我们复习的时候,手头上必须备着相关的参考书籍,比如我们去教室复习的时候,就得背着一大摞书去复习,这样我们复习的效率可能就没有那么高了。

这对应到我们的动态链接与静态链接上是不是就很好理解了:

静态链接与动态链接的主要优缺点

(1)静态链接的优缺点:

优点:

  • 代码装载速度快,执行速度略比动态链接库快;

缺点:

  • 使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费。

(2)动态链接的优缺点:

优点:

  • 生成的可执行文件较静态链接生成的可执行文件小;

缺点:

  • 使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息;
  • 速度比静态链接慢;

动态、静态链接实验

我们先编写如下代码(共三个文件):

文件1(main.c):

#include "test.h"

int main(void)
{
print_hello();
return 0;
}

文件2(test.c):

#include "test.h"

void print_hello(void)
{
printf("hello world\n");
}

文件3(test.h):

#ifndef __TEST_H
#define __TEST_H

#include <stdio.h>

void print_hello(void);

#endif

1、动态链接实验

首先,将源文件生成目标文件(*.o),命令:

gcc -c -fPIC main.c test.c

这里得根据实际编译环境加上或者不加上-fPIC参数,这个是与gcc的版本有关,像我这边的gcc 5.4.0就得显示加上-fPIC这个参数,若是不加,则会影响下一步的链接过程。


在Linux中,动态库的扩展名一般为.so。针对上面生成的test.o文件,生成动态库的命令为:

gcc -shared test.o -o libtest_d.so


若是上一步不加-fPIC参数,则会产生如下错误:


大概意思就是.rodata不可以拿来制作共享文件,请加上-fPIC参数重新编译。问题分析:

-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址。

故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

从gcc来看,shared应该是包含fPIC选项的,但似乎不是所有的版本都支持,所以最好显式加上fPIC选项。

使用链接动态库的方式生成可执行程序,命令:

gcc main.o -L. -ltest_d -o test_d.out


这里的-L.的含义是在搜索库文件时包含当前目录,-ltest_d的含义是链接名称为libtest_d.so的动态链接库。

下面运行test_d.out程序,发现出现如下错误:


不能找到共享库文件libtest_d.so,加载失败。因为一般情况下Linux会在/usr/lib路径中搜索需要用到的库,而libtest_d.so库并不在这个路径下。

解决方法有两种:一种就是把这个文件拷贝至/usr/lib路径下,但是一般不允许这样做,一般用户也不允许往这个路径里拷贝东西。另一种就是把当前路径增加为动态库的搜索路径,命令为:

export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH

这时候就可以正常运行了:

2、静态链接实验

静态库用ar工具来制作。ar是一个归档工具,用于建立、修改、提取归档文件(archive)。

一个归档文件可以包含多个目标文件,也被称为静态库。在Linux下,静态库的扩展名一般为.a

把目标文件test.o做成静态库,命令:

ar -rv libtest_s.a test.o


其中rv参数为组合参数,其中r参数表示当建立的模块名已经存在时,则覆盖同名模块,v参数用来显示附加信息,比如被处理的文件的名字。

使用链接静态库的方法生成可执行程序,命令:

gcc main.o -L. -ltest_s -o test_s.out


运行程序:


删除静态库之后,可执行程序也是能正常运行的。事实上,使用链接静态库的方式生成的可执行程序与直接使用目标文件生成的可执行程序没有区别。

只是经过了静态库的链接,变为了一个文件,方便于调用、移植和保存。

归档工具ar可以很方便地查看和删除归档文件中的成员。

查看静态库libtest_s.a中的内容,命令:

关于ar工具更多的命令参数可输入ar --help进行查看:

最后

以上就是关于静态链接与动态链接的Linux笔记,如有错误,欢迎指出!如果觉得文章不错,转发、在看,也是我们继续更新得动力。


来源:嵌入式大杂烩,作者ZhengN

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存