17

linux下的C开发7,动态链接库和静态链接库有哪些区别?

 4 years ago
source link: https://blog.popkx.com/linux%E4%B8%8B%E7%9A%84c%E5%BC%80%E5%8F%917-%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93%E5%92%8C%E9%9D%99%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93%E6%9C%89%E5%93%AA%E4%BA%9B%E5%8C%BA%E5%88%AB/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前面花了 6 节介绍如何搭建基本的 linux 下的 C语言开发环境,现在终于可以愉快的进行 C语言程序开发了。小编决定先介绍下 linux 下常用的一些库函数,一来可以熟悉 linux 中有哪些现成的轮子可用,二来可以锻炼一下我们的 C语言编程水平,毕竟这系列文章是面向 C语言初学者,而不是大牛的。

48e1b32ecdf6a714812b4a49a5c82d44.png

在 linux 下查看库函数的文档

linux 中有大量的库函数和系统函数可用,这么庞大的函数量,我们不可能全部记下来。而且,即使是一些常用的函数,有时也会记不清它需要包含什么头文件,它的参数类型是什么样的,以及它有哪些返回值。

这时,查看函数的说明文档就很必要了。本系列文章前面几节介绍安装的 ubuntu linux 环境,非常贴心的提供了 man 手册,所以如果我们想查看某个函数的说明文档,直接输入 man 命令查询即可,例如查询 printf 的文档,只需输入

# man 3 printf

就什么都有了,文档非常详细,包含了所需头文件,函数原型,功能描述,返回值等信息,有些函数甚至还会附有示例代码。

feddb6697ce9518ffd4091a391ba4c23.png

所以说 linux 对程序员非常友好,连函数文档都能一键查询。

动态链接库和静态链接库

使用我们按照之前几节配置好的 vim 输入以下代码:

// 文件名 t.c
#include <stdio.h>
int main()
{
    printf("hello embedTime\n");
    return 0;
}

这段代码包含了 stdio 头文件,调用了库函数 printf,所以编译它肯定会使用链接库。linux 系统有两种链接库,一种常常被称为“静态链接库(static library)”,还有一种常被称作“动态链接库(shared library)”。

动态链接是应用非常广泛的方式。动态链接库的英文字面意思可以翻译为“共享的库”,的确如此,使用动态链接库的程序在加载时,linux 内核会检查程序用到的库是否已经在内存中,如果在,则 linux 内核不再重新加载库,直接就执行程序了。所以,多个程序可以共享一个库,这实际上可以节约资源。

对于静态链接库来说,程序链接时会将其作为程序的一部分,因此最终生成的可执行程序相比于动态链接方式,会更大一点。

编译上面的程序:

# gcc t.c -o shared.out

这条编译语句使用的是动态链接方式。为 gcc 命令附加 -static 命令,可以以静态链接方式编译程序:

# gcc t.c -static -o static.out

现在我们查看一下这两种链接方式生成的可执行程序大小对比:

# ls -ahl
total 888K
drwxr-xr-x 3 root root 4.0K Dec 17 22:40 .
drwxr-xr-x 8 root root 4.0K Dec 11 10:28 ..
drwxr-xr-x 2 root root 4.0K Dec 17 22:39 his
-rwxr-xr-x 1 root root 8.4K Dec 17 22:40 shared.out
-rwxr-xr-x 1 root root 857K Dec 17 22:40 static.out
-rw-r--r-- 1 root root   76 Dec 17 21:37 t.c
288393eb65ad22701028eba2ddf6adde.png

很容易看出,使用静态链接方式生成的可执行程序,要比使用动态链接方式生成的可执行程序大 100 多倍。虽然几百 KB 对于大多数 linux 主机来说不算什么,但是嵌入式系统资源一般都非常紧缺,这时再轻易使用静态链接就非常奢侈了。

使用静态链接也是有好处的,生成的可执行程序能够脱离库独立运行,而使用动态链接的可执行程序则不能脱离库独立运行。

静态链接和动态链接的可执行程序,执行过程有哪些不同

上面讨论了 linux 中程序链接的两种方式,既然可执行程序体积相差这么多,那它们的执行过程也应该有所差异了?的确如此,现在我们一起来分析下。在linux中分析程序的执行过程,可以使用 strace 命令。

先分析 shared.out,我们输入 strace ./shared.out,会发现有一大堆输出信息:

# strace ./shared.out
execve("./shared.out", ["./shared.out"], [/* 22 vars */]) = 0
brk(0)                                  = 0x1a66000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=33518, ...}) = 0
mmap(NULL, 33518, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe241ff2000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P \2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1857312, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe241ff1000
mmap(NULL, 3965632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe241a10000
mprotect(0x7fe241bce000, 2097152, PROT_NONE) = 0
mmap(0x7fe241dce000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1be000) = 0x7fe241dce000
mmap(0x7fe241dd4000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe241dd4000
close(3)                                = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe241fef000
arch_prctl(ARCH_SET_FS, 0x7fe241fef740) = 0
mprotect(0x7fe241dce000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7fe241ffb000, 4096, PROT_READ) = 0
munmap(0x7fe241ff2000, 33518)           = 0
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 2), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe241ffa000
write(1, "hello embedTime\n", 16hello embedTime
)       = 16
exit_group(0)                           = ?
+++ exited with 0 +++

这些输出信息即为 linux 执行程序的过程。每一个函数,都可以通过 man 命令查询其手册。几个主要的过程如下:

84e9a7fe37914146daa3c3891a4050e9.png

就是加载库到内存,再执行程序,最后调用系统调用 exit 结束程序。

现在再来看看静态链接的程序 static.out,同样使用 strace 命令查看:

# strace static.out

可以看出,因为链接时,编译器直接把静态库作为程序的一部分了,所以这里相比于动态链接的程序,少了很多将库映射到内存的操作:

606298c12f19ff6e7fbd4983ce5de86e.png

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK