计算机系统篇之链接:静态链接(上)

Posted csstormq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了计算机系统篇之链接:静态链接(上)相关的知识,希望对你有一定的参考价值。

计算机系统篇之链接(3):静态链接(上)

Author:stormQ

Saturday, 21. December 2019 11:58AM


引入静态库的动机

引入静态库是为了更好地解决“编译器开发者如何将标准库函数提供给调用者使用”的问题。

解决该问题的不同方式及比较如下所示:

解决方式优点缺点
方式1:编译器识别程序中对标准库函数的调用并直接生成相应的代码
  • 对调用者而言,最方便。因为标准库函数总是可用的,不需要调用者做任何事情。
  • 增加编译器的复杂性。因为如果采用这种方式,编译器的开发者需要识别程序中调用了哪些标准库函数,并直接生成相应的代码。由于 C 标准库函数很多,所以这种方式将大大增加编译器的复杂性。
  • 不利于编译器升级维护。因为对标准库函数的增、删、改,都要发布一个新版本的编译器。
方式2:将所有的标准库函数实现放到同一个可重定位目标文件中(即 .o 文件)
  • 将标准库函数的实现与编译器的实现解耦。
  • 对调用者而言,比较方便。因为调用者只需要额外链接一个可重定位目标文件。
  • 浪费磁盘空间。每个可执行目标文件都包含一份完整的标准库函数实现的拷贝,即使有些库函数并没有用到。
  • 浪费内存空间。每个程序执行时都会拷贝一份完整的标准库函数实现到内存中,即使有些库函数并没有用到。
  • 增加库函数开发者开发和维护的成本。对任意库函数的任何改动,都需要重新编译所有的库函数实现。
方式3:将每个标准库函数实现放到一个独立的可重定位目标文件中,即一个可重定位目标文件中只存放一个库函数的实现
  • 标准库函数实现与编译器实现解耦,从而解决了方式1的缺点。
  • 只需要重新编译修改过的库函数,同时避免了未使用的库函数占用磁盘和内存空间的问题,从而解决了方式2的缺点。
  • 对调用者而言,最不方便。调用者需要手动链接所有需要的可重定位目标文件,这一过程容易出错并且花费比较长的时间。
方式4:静态库
  • 在链接期,链接器从静态库中只拷贝程序需要的可重定位目标文件。从而避免了未用到的库函数的磁盘和内存空间浪费。
  • 对调用者而言,比较方便。调用者只需要链接少量的静态库,不容易出错且节省时间。
  • 软件维护不简易。如果静态库升级了,那么可执行目标文件必须显式地与更新了的静态库重新链接。
  • 磁盘空间仍有一定的浪费。如果静态库以静态链接的方式(即链接时添加--static选项)生成可执行目标文件,那么不同的可执行目标文件中可能存在相同的可重定位目标文件,从而造成一定的磁盘空间浪费。
  • 内存空间仍有一定的浪费。不同的可执行目标文件中可能存在相同的可重定位目标文件,这些相同的可重定位目标文件会被拷贝到其运行进程的代码段中,从而造成一定的内存空间浪费。

如何生成静态库

静态库的文件格式被称为存档(archive),以.a为后缀。archive 是一组连接起来的可重定位目标文件的集合,有一个头部用于描述每个成员可重定位目标文件的大小和位置。

生成静态库的命令:

$ ar rs <target static library> <object file 1> <object file n>
# $ ar rs libtest.a sum.o test.o

注:操作码r表示将指定的可重定位目标文件插入到静态库中,如果没有静态库则创建。修饰码s表示将索引(ar为可重定位目标文件的每个符号创建一个索引)添加到归档文件中,或者更新它(如果它已经存在)。建立索引的目的:可以加速到库的链接,并允许库中的函数相互调用,而不考虑它们在归档文件中的位置。

1)查看静态库中有哪些可重定位目标文件

# 查看静态库中所有的可重定位目标文件
$ ar t /usr/aarch64-linux-gnu/lib/libc.a
# 查看静态库中指定的可重定位目标文件
$ ar t /usr/aarch64-linux-gnu/lib/libc.a printf.o scanf.o

注:操作码t只显示可重定位目标文件的名称。如果要显示其他信息,比如:可重定位目标文件的大小、文件权限等,需要添加修饰码v,即$ ar tv /usr/aarch64-linux-gnu/lib/libc.a

2)从静态库中提取可重定位目标文件(即将指定的可重定位目标文件从静态库中拷贝到磁盘上)

# 从静态库中提取所有的可重定位目标文件
$ ar x /usr/aarch64-linux-gnu/lib/libc.a
# 从静态库中提取指定的可重定位目标文件
$ ar x /usr/aarch64-linux-gnu/lib/libc.a printf.o scanf.o

3)从静态库中删除指定的可重定位目标文件

$ ar d /usr/aarch64-linux-gnu/lib/libc.a printf.o scanf.o

4)查看ar为可重定位目标文件中的符号创建的索引

$ nm --print-armap libtest.a

Archive index:
_Z3sumii in sum.o
_Z4funcv in test.o

sum.o:
0000000000000000 T _Z3sumii

test.o:
                 U g_val_1
                 U g_val_2
0000000000000000 T _Z4funcv

如何使用静态库

1)查看源文件的源码

$ cat sum.cpp 
int sum(int a, int b)

  return a + b;

$ cat test.cpp 
extern int g_val_1;
extern int g_val_2;

void func()

  g_val_1 *= 2;
  g_val_2 *= 2;

$ cat main.cpp 
#include <stdio.h>

int g_val_1;
int g_val_2 = 3;

void func();

int main()

  printf("original value: g_val_1=%d, g_val_2=%d\\n", g_val_1, g_val_2);
  func();
  printf("now value: g_val_1=%d, g_val_2=%d\\n", g_val_1, g_val_2);
  return 0;

2)生成可重定位目标文件

$ g++ -c test.cpp sum.cpp

注:生成可重定位目标文件分别为test.osum.o

3)生成静态库

$ ar rs libtest.a test.o sum.o

4)使用静态库

方式1:

# main 可以直接加载到内存并运行,在加载时无需更进一步的链接
$ g++ -o main main.cpp --static ./libtest.a

方式2:

# main_2 可以直接加载到内存并运行,在加载时无需更进一步的链接
$ g++ -o main_2 main.cpp --static -L. -ltest

方式3:

# main_3 在加载时需要更进一步的链接
$ g++ -o main_3 main.cpp ./libtest.a
  • 注:

    ---static选项指示编译器驱动程序,链接器应该构建一个完全链接的可执行目标文件,它可以直接加载到内存并运行,在加载时无需更进一步的链接。

    --ltestlibtest.a的缩写。

    --L.指示链接器在当前目录下查找libtest.a

5)运行可执行目标文件

$ ./main
original value: g_val_1=0, g_val_2=3
now value: g_val_1=0, g_val_2=6
$ ./main_2
original value: g_val_1=0, g_val_2=3
now value: g_val_1=0, g_val_2=6
$ ./main_3
original value: g_val_1=0, g_val_2=3
now value: g_val_1=0, g_val_2=6

如果你觉得本文对你有所帮助,欢迎关注公众号,支持一下!

以上是关于计算机系统篇之链接:静态链接(上)的主要内容,如果未能解决你的问题,请参考以下文章

计算机系统篇之链接:动态链接

计算机系统篇之链接:静态链接(中)——符号解析

计算机系统篇之链接:静态链接(下)——重定位

计算机系统篇之链接:静态链接(下)——重定位

计算机系统篇之链接:gcc/g++的编译流程

计算机系统篇之链接:gcc/g++的编译流程