嵌入式C语言自我修养:编译链接
源文件生成可执行文件的过程?
源文件经过预处理、编译、汇编、链接生成一个可执行的目标文件。
编译器驱动程序,包括预处理器、编译器、汇编器和链接器。Linux用户可以调用GCC驱动程序来完成整个编译流程。
使用GCC驱动程序将示例程序从ASCII码源文件转换成可执行目标文件的步骤。
编译执行:
gcc -Og -o prog main.c sum.c ./prog
- 预处理:调用预处理器,对源文件main.c进行预处理,去除注释、展开宏定义等,生成一个中间的ASCII码文件main.i。
- 编译:调用C编译器,将预处理后的中间文件main.i转换成ASCII码的汇编语言文件main.s。
编译阶段是指编译器读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码。
- 汇编:调用汇编器,将汇编语言文件main.s翻译成可重定位目标文件main.o。同样的过程也会应用于sum.c,生成相应的sum.o。
- 链接:调用链接器将main.o和sum.o以及其他必要的系统库文件链接在一起,创建出最终的可执行目标文件prog。
- 加载:将可执行程序加载到内存并进行执行。当shell执行./prog命令时,操作系统内的加载器(loader)将会把可执行文件prog中的代码和数据加载到内存中,并设置好运行环境,然后将控制权转移给程序的入口点,从而启动程序执行(详细见程序的加载)
什么是链接?
链接是将不同目标模块整合为单一的可执行文件。链接可以在程序的不同阶段进行。静态链接编译期间进行的链接,动态链接在运行期间进行的链接。
通过链接可以把程序分散到不同的小的源代码中,而不是一个巨大的类中。这样可以复用常见的功能。
目标文件(模块)由段组成(见ELF格式),主要有以下三种类型:
(1)可重定位目标文件
由汇编器处理后的机器码和数据(.s->.o),还没有进行重定位和符号解析。多个可重定位目标文件可以合并为一个单一的可执行目标文件。
(2)可执行目标文件
可以直接被操作系统加载到内存中执行。
共享目标文件
指动态链接库,在Linux系统中表现为.so文件,在Windows系统中表现为.dll文件。它们同样是可重定位目标文件的一种,但在程序运行时动态加载到内存,并与应用程序进行链接。这种机制允许多个进程共享同一份代码,节省资源并提高内存利用率。
目标文件有统一的格式ELF,可重定位目标文件常见的段包括:
.text | 代码段(指令、机器码) |
.rodata | 只读数据 |
.data | 已初始化的全局和静态变量。 |
.bss | 未初始化(或初始化为0)的全局变量和静态变量。 |
symtab | 符号表,存放在程序中定义和引用的函数与全局变量的信息(局部变量不存放) |
.rel.text | .text节的重定位信息,当链接时,指令的位置就被修改 |
.rel.data | .data节的重定位信息,当链接时,变量的位置就被修改 |
.debug | 调试符号表 |
Section Header Table | 节头部表,存放每个节的偏移和大小 |
符号表记录了模块内定义和引用的各种符号信息。符号表存放如下三种符号:全局变量、外部符号,静态全局变量,这些符号对本模块是随处可见的,但不能被其他模块所引用。局部变量会由栈来管理。局部静态变量,会存放到.bss或者.data节中,也不由符号表来管理。
链接的工作:符号解析和重定位。
(1)符号解析
符号解析把所有目标文件中每一个符号引用都能够绑定到对应的符号定义上。按顺序需要遍历所有输入的目标文件,找出并解决符号依赖关系,确保没有未定义的外部引用。
(2)重定位(Relocation)
编译器和汇编器在生成目标文件时,会为代码和数据分配相对的虚拟地址空间。链接器需要确定每个符号的实际内存地址,并据此更新目标文件中所有的地址引用。利用汇编器产生的重定位表,对每个引用该符号的位置进行调整,使它们指向正确的运行时内存位置。
符号解析:链接器会处理三种符号全局符号(全局变量)、外部符号(外部变量)、本地符号(静态符号)。
编译器只允许模块中的本地符号只有一个定义。当编译器遇到一个不是在本模块中定义的符号时,假设在某个其他模块中定义的,并把后续工作交给链接器处理。当编译器遇到不在当前模块中定义的符号引用时,链接器遍历所有输入目标文件,寻找匹配的全局符号定义。若找不到定义,则会报错终止链接过程。
符号冲突:具有相同全局符号名但在不同目标文件中都有定义的情况,链接器需要解决符号冲突。一次定义原则:即同一个全局符号只能有一个有效定义。
强符号函数和初始化的全局变量。弱符号未初始化的全局变量。不允许多个强符号名称重复。若一个强符号和多个弱符号名称重复,选择强符号;若多个弱符号名称重复,从中任选其一。
关于重载函数的链接问题,译器将函数的名称和参数类型信息一起编码成一个独一无二的、对链接器友好的名称。
重定位
重定位的任务是将输入的各个目标模块合并成一个单一的可执行文件,并为每个符号分配运行时内存地址。链接器将所有输入模块中相同类型的节合并成一个新的聚合节。(所有输入模块的.data节会被合并成输出可执行文件中的一个.data节)。链接器为这个新的聚合节、输入模块定义的每个节以及每个符号赋予运行时内存地址。这样一来,程序中的每条指令和全局变量都将拥有一个唯一的运行时内存地址。
链接器会对代码节和数据节中的每个符号引用进行修改,指向正确的运行时地址。为此,链接器依赖于可重定位目标模块中预先定义好的重定位条目(relocation entry)。重定位条目是一种数据结构,它记录了目标模块中需要被修改的位置以及如何修改这些位置以适应运行时环境的信息。链接器根据这些重定位条目,逐一修改符号引用,确保在程序执行时能够准确寻址到对应的符号定义。重定位条目:在汇编器生成目标模块时,由于它无法预知模块中的数据和代码在内存中的具体加载位置,也无法确定模块所引用的外部函数或全局变量的具体位置,因此每当遇到这类不确定位置的引用时,汇编器会在目标文件中生成相应的重定位条目。这些重定位条目指示链接器在合并目标文件生成可执行文件的过程中如何正确地修正这些引用。
静态链接
静态链接在程序运行之前,将目标模块和所需的静态库链接成一个完整的可执行文件。静态库将一组相关的可重定位目标文件(.o文件)打包成单个.a文件。(Linux系统中,静态库以一种存档的特殊文件存放在磁盘中,存档文件由.a标识)。
模块化:将函数编译为独立的目标模块,便于管理和更新。
(2) 按需链接:链接器在链接过程中仅将应用程序实际引用的库模块加入到最终可执行文件中,避免了资源冗余。
链接器是如何使用静态库解析引用?
如果静态库之间是相互独立的,库可以任意顺序排列在命令行的末尾。如果库之间存在依赖关系,即某个库的成员引用了另一个库中定义的符号,那么必须按照依赖关系正确排序库的顺序。因为链接是按顺序扫描目标文件和静态库文件,所以可能存在引用依赖问题的,写编译命令的时候,需要按顺序写。在命令行中,如果定义一个符号的库在引用这个符号的目标文件之前,引用就不会被解析(因为加到了未解析符号引用的集合),因此链接会失败。
动态链接共享库
静态库虽然有效地解决了函数库复用的问题,但存在一些局限性:
(1)如果静态库中有bug 修复时,所有依赖它的应用程序都需要重新编译并链接到更新后的库版本。(改动后需要重写编译)
(2)内存资源浪费:静态链接导致每个使用标准库的应用程序都会包含库函数的副本。当系统运行很多进程时,这些重复的代码片段占用内存资源。
动态链接
程序运行时第一次调用某个库函数的时候,由动态链接器负责将程序与共享库链接起来。共享库是存放在磁盘上的对象(unix .so,Windows DLL),可以在运行时使用内存映射的方式加载到程序的共享内存映射区域。
动态链接也可以在程序开始执行的时候完成,在Linux 中使用 dlopen()接口来完成(会使用函数指针),而且共享库也可以在多个进程间共享。
共享库(动态链接)的优点
(1)内存优化:共享库中的代码在物理内存中只有一份拷贝,所有需要它的进程都可以共享同一份代码,显著减少了内存开销。
(2)当共享库有更新时,只需要替换系统上的库文件即可。已安装的应用程序在下次运行时会自动链接到新版本的库,无需重新编译或重新发布整个应用程序。
在Linux系统中,运行时动态加载和链接共享库的功能通常通过dlopen() 和dlsym() 等函数实现,这些函数属于动态链接器接口的一部分。dlopen() 用于打开并加载指定的共享库,而 dlsym() 则用于从已加载的库中获取函数指针,以便后续调用。 C标准库默认使用动态链接。编译时候加-static选项可以使用静态链接。
动态链接库如何加载到内存中的?
当动态库第一次被链接器加载到内存参与动态链接时,动态库映射到了当前进程虚拟空间的mmap区域,动态链接和重定位结束后,程序就开始运行。当程序访问mmap映射区域,去调用动态库的一些函数时,发现此时还没有为这片虚拟空间分配物理内存,就会产生一个请页异常。内核接着会为这片映射内存区域分配物理内存,将动态库文件libtest.so加载到物理内存,并将虚拟地址和物理地址之间的映射关系更新到进程的页表项,此时动态库才真正加载到物理内存,程序才可以正常运行。
对于已经加载到物理内存的文件,Linux内核会通过一个radix tree(基数树)的树结构来管理这些页缓存对象。当进程B运行也需要加载动态库libtest.so时,动态链接器会将库文件libtest.so映射到进程B的一片虚拟内存空间上,链接重定位完成后进程B开始运行。当通过映射内存地址访问libtest.so时也会触发一个请页异常,Linux内核在分配物理内存之前会先从radix tree树中查询libtest.so是否已经加载到物理内存,当内核发现libtest.so库文件已经加载到内存后就不会给进程B分配新的物理内存,而是直接修改进程B的页表项,进程B中的这片映射区域直接映射到libtest.so所在的物理内存上。
基数树阅读材料:Radix Tree用法-CSDN博客
打包静态库:
gcc add.c -c -o add.o
gcc swap.c -c -o swap.o
ar rcs打包静态库
ar rcs libcalc.a swap.o add.o
打包动态库:使用gcc在链接的时候生成共享库文件(.so)
相关文章:

嵌入式C语言自我修养:编译链接
源文件生成可执行文件的过程? 源文件经过预处理、编译、汇编、链接生成一个可执行的目标文件。 编译器驱动程序,包括预处理器、编译器、汇编器和链接器。Linux用户可以调用GCC驱动程序来完成整个编译流程。 使用GCC驱动程序将示例程序从ASCII码源文件转换…...

Mac制作Linux操作系统启动盘
前期准备 一个 Mac 电脑 一个 U 盘(8GB 以上) 下载好 Linux 系统镜像(iso 文件) 具体步骤 挂载 U 盘 解挂 U 盘 写系统镜像到 U 盘 完成 一、挂载 U 盘 首先插入 U 盘,打开终端输入下面的命令查看 U 盘是否已经 m…...
PHP语言发展历程
PHP是一种开源的服务器端脚本语言,主要用于Web开发,最初由Rasmus Lerdorf在1994年创建。PHP的发展历程如下: PHP的起源:1994年,Rasmus Lerdorf创建了PHP的第一个版本,最初是一套用于跟踪他个人简历访问的C…...

Notepad++ 之 AndroidLogger插件
背景 最近一段时间在分析Android log 定位问题,Notepad 之前用的比较少,现在看log觉得确实好用,美中不足的是 看Android log的时候不像 logcat -v color 可以区分不同等级的颜色,于是调研了一下,发现大部分都是使用An…...

开源2+1链动模式AI智能名片O2O商城小程序源码:线下店立体连接的超强助力器
摘要:本文将为您揭示线下店立体连接的重大意义,您知道吗?线上越火,线下就得越深入经营。现代门店可不再只是卖东西的地儿,还得连接KOC呢!咱们来看看门店要做的那些超重要的事儿,还有开源21链动模…...

我为什么决定关闭ChatGPT的记忆功能?
你好,我是三桥君 几个月前,ChatGPT宣布即将推出一项名为“记忆功能”的新特性,英文名叫memory。 这个功能听起来相当吸引人,宣传口号是让GPT更加了解用户,仿佛是要为我们每个人量身打造一个专属的AI助手。 在记忆功…...

如何使用ssm实现中学生课后服务的信息管理与推荐+vue
TOC ssm766中学生课后服务的信息管理与推荐vue 第一章 绪论 1.1 选题背景 目前整个社会发展的速度,严重依赖于互联网,如果没有了互联网的存在,市场可能会一蹶不振,严重影响经济的发展水平,影响人们的生活质量。计算…...
【分别为微服务云原生】9分钟ActiveMQ延时消息队列:定时任务的革命与Quartz的较量
ActiveMQ延时消息队列:定时任务的革命与Quartz的较量 摘要: 在现代的消息驱动架构中,ActiveMQ的延迟消息队列功能为定时任务提供了一种新的解决方案。本文将详细介绍ActiveMQ延迟消息队列的功能、应用场景,并与Quartz定时任务进行…...

泛型编程--模板【C++提升】(特化、类属、参数包的展开、static、模板机制、重载......你想知道的全都有)
更多精彩内容..... 🎉❤️播主の主页✨😘 Stark、-CSDN博客 本文所在专栏: C系列语法知识_Stark、的博客-CSDN博客 其它专栏: 数据结构与算法_Stark、的博客-CSDN博客 C系列项目实战_Stark、的博客-CSDN博客 座右铭:梦…...

安卓使用memtester进行内存压力测试
memteser简介 memtester 是一个用于测试内存可靠性的工具。 它可以对计算机的内存进行压力测试,以检测内存中的错误,例如位翻转、随机存取错误等。memtester 可以在不同的操作系统上运行,并且可以针对不同大小的内存进行测试。 下载源码 m…...
Dave Cheney: Go语言之禅
本篇内容是根据2020年3月份The Zen of Go音频录制内容的整理与翻译, Dave Cheney 讲述了 Go 之禅(编写简单、可读、可维护的 Go 代码的十个工程价值)。是什么让 Go 代码变得优秀?编写 Go 代码时,我们应该牢记哪些指导原则&#x…...

SpringMVC源码-AbstractUrlHandlerMapping处理器映射器将实现Controller接口的方式定义的路径存储进去
DispatcherServlet的initStrategies方法用来初始化SpringMVC的九大内置组件 initStrategies protected void initStrategies(ApplicationContext context) {// 初始化 MultipartResolver:主要用来处理文件上传.如果定义过当前类型的bean对象,那么直接获取࿰…...

满填充透明背景二维码生成
前几天项目上线的时候发现一个问题:通过Hutool工具包生成的二维码在内容较少时无法填满(Margin 已设置为 0)给定大小的图片。因此导致前端在显示二维码时样式异常。 从图片中我们可以看到,相同大小的图片,留白内容是不一样的。其中上半部分…...

Python | Leetcode Python题解之第452题用最少数量的箭引爆气球
题目: 题解: class Solution:def findMinArrowShots(self, points: List[List[int]]) -> int:if not points:return 0points.sort(keylambda balloon: balloon[1])pos points[0][1]ans 1for balloon in points:if balloon[0] > pos:pos balloo…...

代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作删除二叉搜索树中的节点修剪二叉搜索树
代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作&&删除二叉搜索树中的节点&&修剪二叉搜索树 主要学习内容: 二叉搜索树的插入删除操作 701.二叉搜索树中的插入操作 701. 二叉搜索树中的插入操作 - 力扣(LeetCode&…...

使用Apifox创建接口文档,部署第一个简单的基于Vue+Axios的前端项目
前言 在当今软件开发的过程中,接口文档的创建至关重要,它不仅能够帮助开发人员更好地理解系统架构,还能确保前后端开发的有效协同。Apifox作为一款集API文档管理、接口调试、Mock数据模拟为一体的工具,能够大幅度提高开发效率。在…...

TCP的第三次握手没有回复,会出现哪些问题现象
从三次握手的一开始来讲,刚开始客户端和服务器都处于close状态 这里不能是2次握手的原因就在于,服务器端即女孩子,无法确认客户端即男孩子,是否已经收到了,我也愿意建立连接即我也爱你,这一条最终确认的信息…...
【工具】arxiv_latex_cleaner 去除latex注释
https://github.com/google-research/arxiv-latex-cleaner/issues/24 文章目录 1.修改编码2.如何安装2.1.打包2.2.安装 3.测试功能 注意:需要创建python3.9的环境 1.修改编码 官方提供的arxiv_latex_cleaner的编码格式是有问题的,见这里。这个有位朋友说…...
macOS开发环境配置与应用开发
一、macOS开发环境配置 1. 安装Xcode Xcode 是Apple官方开发环境工具,用于macOS、iOS、watchOS和tvOS应用开发。它集成了代码编辑、编译、调试、性能分析、界面设计等功能。 下载与安装: 打开 App Store,搜索“Xcode”。 点击安装ÿ…...

15分钟学 Python :编程工具 Idea 和 vscode 中配置 Python ( 补充 )
编程工具配置 Python 在 IDE 和 VSCode 中 在编程学习的过程中,选择合适的开发工具至关重要。本文将详细介绍在两种流行的IDE(IntelliJ IDEA 和 Visual Studio Code)中如何配置Python环境,帮助你更高效地进行Python开发。 一、编…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

如何在Windows本机安装Python并确保与Python.NET兼容
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...