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

Author: stormQ

Created: Saturday, 21. December 2019 11:58AM

Last Modified: Thursday, 05. November 2020 11:00PM



摘要

本文描述了引入静态链接库的动机,并提供了 Linux 系统中生成和使用静态链接库的示例。

引入静态库的动机

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

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

解决方式 优点 缺点
方式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

    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

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

    上一篇:计算机系统篇之链接(2):目标文件

    首页