百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Linux 下静态链接和动态链接的原理及应用

bigegpt 2024-08-05 11:46 2 浏览

我们知道一个.c 文件经过编译、链接最终可以形成一个可执行文件。


链接原理

当我们的程序包含多个文件时,那么这些文件是怎么形成一个目标文件的呢?

这就要涉及到链接器。

链接器的作用就是将多个目标文件链接成一个完整、可加载、可执行的目标文件。其输入是一组可以重定位的目标文件。

链接的主要作用有2个:

符号解析:将目标文件内的引用符号和该符号的定义联系起来。

将符号定义与存储器的位置联系起来,修改对这些符号的引用。


目标文件

典型的目标文件有3种形式

可重定位目标文件:这种目标文件后缀通常为.o,这种文件包含已经转换成机器指令的二进制代码和数据,但是这种文件还不能直接执行,因为这些指令和数据中往往还引用其他模块(目标文件)中的符号,这些其他模块的符号对于本模块来讲还都是未知的,因此这些符号的解析需要链接器对这些模块进行连接,这种操作也称为“重定位”。

可执行目标文件:这种文件同样包含二进制代码和数据,区别就是这些文件已经经过链接,因此这些文件是可以直接执行的。

共享目标文件:这是一种特殊类型的可重定位的目标文件,可以在需要它的程序运行过程或者加载时,动态的加载到内存中运行。这种文件的后缀通常为“.so”, 共享目标文件又称为“动态库”文件或者“共享库”文件。


符号解析

符号解析是链接的主要任务之一。只有在正确的解析了符号之后才能更改引用符号的位置,从而完成重定位,生成一个可以被机器直接加载执行的的可执行目标文件。每个可重定位目标文件都有一个符号表。在这个符号表中存储符号。这些符号分为3类:

本模块中引用其他模块所定义的全局符号。

本模块中定义的全局符号。

本模块中定义和引用的局部符号。


重定位

在符号解析结束后,每个符号的定义位置及大小都是已知的了。重定位操作只需要将这些符号链接起来。在该步骤中,链接器需要将所有参与链接的目标文件合并,并且为每一个符号分配存储内容的运行时地址。

重定位分为2个步骤进行:

重定位段:该步将所有目标文件中同类型的段合并,生成一个大段。比如,将所有参与链接的目标文件的数据段合并,生成一个大的数据段。合并之后,程序中的指令和变量就拥有一个统一并且唯一的运行时地址了。

重定位符号引用:由于目标文件中相同的段已经合并,因此程序中对符号的引用位置就都作废了。这时链接器需要修改这些引用符号的地址,使其指向正确的运行时地址。

程序库

所谓“程序库”就是包含了一些通用函数的数据和二进制可执行机器码的文件。这些文件是目标文件的一种,其不能单独执行。但是若与其他的可执行程序结合起来就可以执行了。

从链接方式上区别,程序库可分为静态库和动态库(共享库)两种:

静态库:是在可执行程序运行前就已经加入到执行代码中,成为执行程序的一部分来执行的。

动态库:是在执行程序启动时加载到执行程序中,可以被多个执行程序共享使用。

静态库

静态库是一些目标代码的集合。Linux下静态目标文件一般以.a作为目标文件的后缀。在Linux环境下使用ar命令来创建一个静态库。静态库的优点就是在生成时已经编译成可重定位的目标文件,节省了编译时间,并且在编译时把代码复制到可执行代码段中,这样可执行程序就可以单独直接运行,但是缺点也是显而易见的,就是可执行文件可能会变得很臃肿。

静态库的创建

本文以四则运算来创建一个静态库,该静态库中包含四个函数:加、减、乘、除。

// static_lib.c
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}

生成一个可重定位的目标文件。

# gcc -c static_lib.c

在Linux下使用 ar 命令创建一个静态库,或者将目标文件加入到一个已经存在的静态库中。其使用方法如下:

ar rcs 静态库名 目标文件1 目标文件2 ... 目标文件n

该命令表示将目标文件1~n加入到指定的静态库中。若该静态库不存在,则创建静态库文件,并将库文件的扩展名命名为.a, 其中rcs这三个参数分别表示:把列表中的目标文件加入到静态库中(r);若指定的静态库不存在,则创建该库文件(c);更新静态库文件的索引,使之包含新加入的目标文件的内容(s)。

使用生成的 static_lib.o 目标文件创建一个静态库 static_lib.a

# ar rcs static_lib.a static_lib.o

查看生成的静态库文件

# ls
static_lib.a static_lib.c static_lib.o


静态库的使用

创建的静态库需要链接到应用程序中才能使用,为了方便引用,我们创建一个头文件,使用时把该头文件包含到应用程序中。

//static_lib.h
extern int add(int a, int b);
extern int sub(int a, int b);
extern int mul(int a, int b);
extern int div(int a, int b);

编写应用程序

//test.c
#include <stdio.h>
#include "static_lib.h"
int main()
{
int a = 8, b = 4;
printf("the add : %d\n", add(a, b));
printf("the sub : %d\n", sub(a, b));
printf("the mul : %d\n", mul(a, b));
printf("the div : %d\n", div(a, b));
}

编译

# gcc test.c -o test -L. static_lib.a
或者
#gcc test.c -o test ./static_lib.a

运行

# ./test
the add :12
the sub :4
the mul :32
the div :2


动态库

动态库又称为共享库或者动态链接库。在 Linux 环境下为 so 文件。动态库是在程序运行时加载的。当一个应用程序装载了一个动态库后,其他应用程序仍可以装载同一个动态库。这个被多个进程同时使用的动态库在内存中只有一个副本,因此动态库易于程序模块的更新,更新库并不影响应用程序使用旧的、非向后兼容的版本。

创建动态库

我们依然使用以四则运算来创建一个动态库,该动态库中包含四个函数:加、减、乘、除。

// share_lib.c
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}

Linux 下使用 gcc 创建一个动态库。由于动态库可以被多个进程共享加载,所以需要生成位置无关的目标文件。因此需要使用 gcc 编译器的 -fPIC 选项,该选项用于生成位置无关的代码。除了使用 -fPIC 选项,还需要使用 -shared 选项,该选项将位置无关的代码制作为动态库。

创建动态库的方法如下

# gcc -shared -fPIC -o share_lib.so share_lib.c
# ls
share_lib.so share_lib.c

动态库的使用

为了使应用程序可以正确的引用该库中的全局符号,需要制作一个包含该动态库文件中的全局符号声明的头文件。

//share_lib.h
extern int add(int a, int b);
extern int sub(int a, int b);
extern int mul(int a, int b);
extern int div(int a, int b);

编写一个应用程序使用动态库

//main.c
#include <stdio.h>
#include "share_lib.h"
int main()
{
int a = 8, b = 4;
printf("the add : %d\n", add(a, b));
printf("the sub : %d\n", sub(a, b));
printf("the mul : %d\n", mul(a, b));
printf("the div : %d\n", div(a, b));
}

运行

# gcc main.c ./share_lib.so -o main
# ./main
the add :12
the sub :4
the mul :32
the div :2

相关推荐

程序员请收好:10个非常有用的 Visual Studio Code 插件

一个插件列表,可以让你的程序员生活变得轻松许多。作者|Daan译者|Elle出品|CSDN(ID:CSDNnews)以下为译文:无论你是经验丰富的开发人员还是刚刚开始第一份工作的初级开发人...

PADS在WIN10系统中菜单显示不全的解决方法

决定由AD转PADS,打开发现菜单显示不正常,如下图所示:这个是由于系统的默认字体不合适导致,修改一下系统默认字体即可,修改方法如下:打开开始菜单-->所有程序-->Windows系统--...

一文讲解Web前端开发基础环境配置

先从基本的HTML语言开始学习。一个网页的所有内容都是基于HTML,为了学好HTML,不使用任何集成工具,而用一个文本编辑器,直接从最简单的HTML开始编写HTML。先在网上下载notepad++文...

TCP/IP协议栈在Linux内核中的运行时序分析

本文主要是讲解TCP/IP协议栈在Linux内核中的运行时序,文章较长,里面有配套的视频讲解,建议收藏观看。1Linux概述  1.1Linux操作系统架构简介Linux操作系统总体上由Linux...

从 Angular Route 中提前获取数据

#头条创作挑战赛#介绍提前获取意味着在数据呈现在屏幕之前获取到数据。本文中,你将学到,在路由更改前怎么获取到数据。通过本文,你将学会使用resolver,在AngularApp中应用re...

边做游戏边划水: 基于浅水方程的水面交互、河道交互模拟方法

以下文章来源于腾讯游戏学堂,作者Byreave篇一:基于浅水方程的水面交互本文主要介绍一种基于浅水方程的水体交互算法,在基本保持水体交互效果的前提下,实现了一种极简的水面模拟和物体交互方法。真实感的...

Nacos介绍及使用

一、Nacos介绍Nacos是SpringCloudAlibaba架构中最重要的组件。Nacos是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台,提供注册中心、配置中心和动态DNS...

Spring 中@Autowired,@Resource,@Inject 注解实现原理

使用案例前置条件:现在有一个Vehicle接口,它有两个实现类Bus和Car,现在还有一个类VehicleService需要注入一个Vehicle类型的Bean:publicinte...

一文带你搞懂Vue3 底层源码

作者:妹红大大转发链接:https://mp.weixin.qq.com/s/D_PRIMAD6i225Pn-a_lzPA前言vue3出来有一段时间了。今天正式开始记录一下梗vue3.0.0-be...

一线开发大牛带你深度解析探讨模板解释器,解释器的生成

解释器生成解释器的机器代码片段都是在TemplateInterpreterGenerator::generate_all()中生成的,下面将分小节详细展示该函数的具体细节,以及解释器某个组件的机器代码...

Nacos源码—9.Nacos升级gRPC分析五

大纲10.gRPC客户端初始化分析11.gRPC客户端的心跳机制(健康检查)12.gRPC服务端如何处理客户端的建立连接请求13.gRPC服务端如何映射各种请求与对应的Handler处理类14.gRP...

聊聊Spring AI的Tool Calling

序本文主要研究一下SpringAI的ToolCallingToolCallbackorg/springframework/ai/tool/ToolCallback.javapublicinter...

「云原生」Containerd ctr,crictl 和 nerdctl 命令介绍与实战操作

一、概述作为接替Docker运行时的Containerd在早在Kubernetes1.7时就能直接与Kubelet集成使用,只是大部分时候我们因熟悉Docker,在部署集群时采用了默认的dockers...

在MySQL登录时出现Access denied for user ~~ (using password: YES)

Windows~~~在MySQL登录时出现Accessdeniedforuser‘root‘@‘localhost‘(usingpassword:YES),并修改MySQL密码目录适用...

mysql 8.0多实例批量部署script

背景最近一个项目上,客户需要把阿里云的rdsformysql数据库同步至线下,用作数据的灾备,需要在线下的服务器上部署mysql8.0多实例,为了加快部署的速度,写了一个脚本。解决方案#!/bi...