多文件和静态/动态链接以及虚拟内存管理

多目标文件链接

//stack.c
char stack[512];
int top =-1;
void push(char c){stack[++top] = c;
}char pop(void){return stack[top--];
}int is_empty(void){return top == 1;
}// main.c
#include <stdio.h>
int a,b = 1;
int main(){
push('a');
push('b');
push('c');
while(!is_empty())putchar(pop());putchar('\n');return 0;
}

通过 readelf -a main命令可以看到

  • main的.bss段合并了 main.o和stack.o的.bss段,包含了变量a和stack;
  • main的.data段合并了main.o和 stack.o的.data段,其中包含了变量b和top;
  • main的.text段合并了main.o和 stack.o的.text段
image-20240525234842552

PS: GDB如何调试多个文件的code

//main.c 和 stack.c 
(gdb)list stack.c:1 
(gdb)b stack.c:10     

定义和声明

extern和static

  • 用extern声明的函数名具有external linkage
  • 用static声明的函数名具有internal linkage
  • 函数默认是extern的

凡是被多次声明的变量或函数,必须有且只有一个声明是定义,如果有多个定义,或者一个定义都没有,链接器就无法完成链接。

变量声明和函数声明有一点不同,函数声明的 extern关键字可以省略,而变量声明如果不写 extern意思就完全变了

用 static关键字声明具有 Internal Linkage,保护了函数的内部状态,是一种封装

头文件

通过宏定义避免硬编码

//stack.h 
#ifdef STACK_H 
#define STACK_H void push(char); char pop(void); int is_empty(void); 
#endif//main.c 
#include "stack.h"          

对于用角括号包含的头文件**,gcc首先查找**-I**选项指定的目录,然后查找系统的头文件目录);

而对于用引号包含的头文件**,gcc首先查找包含这个头文件的当前文件所在的目录,然后查找**-I选项指定的目录,然后查找系统的头文件目录

则可以用gcc- c maln.c编译,gcc会自动在main.C所在的目录中找到stack. h。假如把 stack.h移到一个子目录下

image-20240525235130253

则需要用gcc- c main.c -Istack编译,用-I选项告诉gcc头文件要到子目录 stack里找

在#include预处理指示中可以使用相对路径,例如把上面的代码改成#include “stack/stack,h”,那么编译时就不需要加-Istack选项了,因为是main.c要包含头文件,gcc会自动在main.c所在的目录中查找,而头文件相对于main.c所在目录的相对路径正是 stack/ stack.h

PS:gcc -E可以产生预编译文件

避免头文件被重复包含的方法为header guard

写.C文件和头文件时一般来说应遵循以下原则:

  • C文件中可以有变量或函数定义,而.h文件中应该只有变量或函数声明而没有定义。
  • 不要把一个C文件包含到另一个C文件中。

静态库

把一组代码编译成一个库,很多项目中复用

例如将stack.c文件拆分为四个文件,main.c保持不变

image-20240525235204471
gcc -c stakc/stack.c stack/push.c stack/pop.c stack/is_empty.c
ar rs libstack.a stack.o push.o pop.o is_empty.o
# r表示将文件打包进libstack.a中,s表示为静态链接库
# 等价于
ar r libstack.a stack.o push.o pop.o is_empty.o
ranlib libstack.a
# 链接libstack.a main.c
gcc mian.c -L. -lstack -Istack -o main

-L选项告诉编译器去哪里找需要的库文件L.表示在当前目录找。-lstack选项告诉编译器要链接 libstack库-I选项告诉编译器去哪里找头文件

编译器默认会找哪些目录,用-print-search-dirs选项查看一下

gcc -print-search-dirs            

在处理-lstack选项时,gcc首先到-L选项指定的目录下查找,看有没有共享库Iibstack.so,如果有就链接它,否则再找有没有静态库 Iibstack,a,如果有就链接它,如果还是没有,就到默认搜索路径下按同样的步骤查找。

gcc在链接时优先考虑共享库,其次才是静态库,如果希望gcc只考虑静态库,可以指定-static选项。

main.c只调用了push这一个函数,所以链接生成的可执行文件中也只有push而没有pop和 is_empty。链接器从静态库中只取出需要的目标文件来做链接,不需要的目标文件可以不链接

共享库

组成共享库的目标文件和一般的目标文件有所不同,在编译时要加-fPIC选项,即位置无关编码

gcc -c -fPIC stakc/stack.c stack/push.c stack/pop.c stack/is_empty.c

指令中凡是用到stack和top变量的地址都用0x0表示,以备在重定位时修改。

image-20240526001510554

原来指令中的0x0被改成了0x804a010和0x804a040,这样做了重定位之后,各段的加载地址就定死了,因为在指令中使用了绝对地址

image-20240526001534124

和先前的结果不同,指令中的0x0(%ebx)被修改成-0xc(ebx)和-0x8(%ebx),而不是修改成绝对地址。所以共享库各段的加载地址并没有定死,可以加载到任意位置。因为指令中的地址都是相对于ebx的,没有使用绝对地址,只要根据实际的加载情况修改ebx就可以了,这就是位置无关代码的特点.

image-20240526001605212

对比前后的指令差异

image-20240525235450330

-0xc(%ebx)这个地址并不是变量top的地址,这个地址的内存单元中又保存了另外一个地址,而它才是变量top的地址。指令mov -0xc(%ebx),%eax是从地址ebx-12取出变量top的地址传给eax,而指令mov (%eax),%eax才是从top的地址取出top的值传给eax。指令lea 0x1(%eax),%edx是把top的值加1存到edx中。lea指令算出第一个操作数所代表的地址,但并不访问内存,而是直接把这个地址传给第二个操作数。我们知道x86的内存寻址方式涉及加法和乘法运算,lea指令只是利用寻址电路做加法和乘法运算,而不是真的寻址,

image-20240526001319076

将main.c文件和共享库链接

image-20240525235509342

用Ldd命令査看可执行文件依赖于哪些共享库:

image-20240525235515592

动态链接器在那些目录搜索共享库?

  1. 首先在环境变量LD_ LIBRARY_PATH保存的路径中查找
  2. 然后从缓存文件/etc/ld.so. cache中查找这个缓存文件是由 ldconfig命令读取配置文件/etc/ld.so.conf生成的
  3. 如果上述步骤都找不到,则到默认的系统库文件目录中查找,先是/usr/ib然后是/Lib。

最常用的方法。把lsibtack.so所在目录的绝对路径(比如/home/ akaedu/somedir)添加到配置文件/etc/ld.so.conf(该文件中每个路径占一行),然后运行ldconfig命令:

image-20240525235606585

再查看动态库

image-20240525235617265

函数的动态链接

和链接静态库的情况不同,push函数的指令没有链接到可执行文件中,而且call 86483d8-push@pLt>这条指令调用的也不是push函数的地址,而是plt段里的地址。PLT是 Procedure Linkage Table的缩写,.plt段里保存的也是指令,和.text一起合并到 Text Segment

image-20240525235657934

共享库命名

按照共享库的命名惯例,每个共享库有三个文件名:real name、 soname和 linker name

真正的库文件(而不是符号链接)的名字是 real name,包含完整的共享库版本号,例如上面的 libcap.so.1.10、libc-2.8.90.so等

soname是符号链接的名字,只包含共享库的主版本号

但对于依赖libcap.So.1的程序来说,真正的库文件不管是 Libcap.S0.1.16还是Libcap.so.1.11都可以用,所以使用共享库可以很方便地升级库文件而不需要重新编译程序,这是静态库所没有的优点。注意libc的版本编号有一点特殊,libc-2.8.90.s0的主版本号是6而不是2或28

linker name仅在编译链接时使用,gcc的-L选项应该指定 linker name所在的目录。有的 linker name是库文件的一个符号链接,有的 linker name是一段链接脚本。

虚拟内存管理

ps //查看进程
cat /porc/29977/maps //查看进程地址空间
image-20240525235734531

堆空间的地址上限(0x09497000)称为 Break,堆空间要向高地址增长就要抬高 Break,映射新的虚拟内存页面到物理内存,这是通过系统调用brk实现的, malloc函数也是调用brk向内核请求分配内存的。

操作系统虚拟内存控制机制的作用

(1)可以控制物理内存的访问权限

物理内存本身是不限制访问的,任何地址都可以读写,而操作系统要求不同的页面具有不同的访问权限,这是利用CPU模式和MMU的内存保护机制实现的。错误的指令或恶意代码的破坏能力受到了限制,最多使当前进程因段错误而终止,不会影响到整个系统的稳定性。

(2)使每个进程有独立的地址空间

不同进程中相同的VA被MMU映射到不同的PA,因此在某一个进程中访问任何虚拟地址都不可能访问到属于另外一个进程的物理内存页面,并且每个进程都认为自己独占0x0000000 xbffffffff 整个用户地址空间。独立地址空间的好处是:任何一个进程由于执行了错误指令或恶意代码而导致的非法内存访问都不会意外改写其他进程的数据,也不会影响其他进程的运行;链接器和加载器的实现也比较容易,不必考虑各进程的地址范围是否冲突

image-20240525235841597

两个进程都是bash进程, Text Segment是一样的,并且 Text Segment是只读的,不会被改写,因此操作系统安排两个进程的TextSegment共享相同的物理页面。由于每个进程都有自己的一套VA到PA的映射表,在一个进程中通过VA只能访问到属于自己的物理页面,而不会访问到其他进程的物理页面。

(3)VA到PA的映射会给分配和释放内存带来方便

物理地址不连续的几块内存可以映射成虚拟地址连续的一块内存。比如要用 malloc分配一块很大的内存空间,虽然有足够多的空闲物理内存,却没有足够大的连续空闲内存,这时就可以分配多个不连续的物理页面而映射到连续的虚拟地址范围。

image-20240525235849454

(4)一个系统如果同时运行着很多进程,为各进程分配的内存之和可能会大于实际可用的物理内存,虚拟内存管理机制使这种情况下各进程仍然能够正常运行

进程访问的是虚拟内存页面,这些页面的数据可以保存在物理页面中,也可以临时保存在磁盘上而不占用物理页面,可以在磁盘上开一个分区或者建一个文件专门用于临时保存虚拟内存页面的数据,这称为交换设备( Swap Device)。启用了交换设备之后,系统中可分配的内存总量等于物理内存的大小与交换设备的大小之和

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/16372.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

应用程序图标提取

文章目录 [toc]提取过程提取案例——提取7-zip应用程序的图标 提取过程 找到需要提取图标的应用程序的.exe文件 复制.exe文件到桌面&#xff0c;并将复制的.exe文件后缀改为.zip 使用解压工具7-zip解压.zip文件 在解压后的文件夹中&#xff0c;在.rsrc/ICON路径下的.ico文件…

代码随想录-Day20

654. 最大二叉树 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。 递归地在最大值 左边 的 子数组前缀上 构建左子树。 递归地在最大值 右边 的 子数组后缀上 构建右子树。 返回 nums…

ROS | 激光雷达包格式

ros激光雷达包格式&#xff1a; C实现获取雷达数据 &#xff1a; C语言获取雷达数据&#xff1a; Python语言获取雷达数据&#xff1a; python不需要编译&#xff0c;但是需要给它一些权限 chmod x lidar_node.py(当前的文件名字&#xff09; C实现雷达避障&#xff1a; python…

【Xilinx】常用的全局时钟资源相关Xilinx器件原语

1 概述 常用的与全局时钟资源相关的Xilinx器件原语包括&#xff1a; IBUFGIBUFGDS、OBUFGDS 和 IBUFDS、OBUFDSBUFGBUFGPBUFGCEBUFGMUXBUFGDLLIBUFDS_GTXE1IBUFDS_GTE2IBUFDS_GTE3OBUFDS_GTE3IBUFDS_GTE4OBUFDS_GTE4DCM 刚开始看到这写源语&#xff0c;免不了好奇这些源语对应的…

IDEA如何对多线程进行debug

开发中使用到多线程的时候不少,但是debug起来还是比较困难的,因为默认每次只会进入一个线程,这样有些问题是发现不了的,其实IDEA也是支持进入每个线程来debug的 写一个简单的demo public class ThreadDebug {public static void main(String[] args) {MyThread myThread new…

异方差的Stata操作(计量114)

以数据集 nerlove.dta 为例&#xff0c;演示如何在 Stata 中处理异方差。 此数据集包括以下变量&#xff1a; tc ( 总成本 ) &#xff1b; q ( 总产量 ) &#xff1b; pl ( 工资率 ) &#xff1b; pk ( 资本的使用成本 ) &#xff1b; pf ( 燃料价格 ) &#xff1b; …

GESP等级大纲

CCF编程能力等级认证概述 CCF编程能力等级认证&#xff08;GESP&#xff09;为青少年计算机和编程学习者提供学业能力验证的规则和平台。GESP覆盖中小学阶段&#xff0c;符合年龄条件的青少年均可参加认证。C & Python编程测试划分为一至八级&#xff0c;通过设定不同等级…

[自动驾驶技术]-6 Tesla自动驾驶方案之硬件(AI Day 2021)

1 硬件集成 特斯拉自动驾驶数据标注过程中&#xff0c;跨250万个clips超过100亿的标注数据&#xff0c;无论是自动标注还是模型训练都要求具备强大的计算能力的硬件。下图是特斯拉FSD计算平台硬件电路图。 1&#xff09;神经网络编译器 特斯拉AI编译器主要针对PyTorch框架&am…

AI数据面临枯竭

Alexandr Wang&#xff1a;前沿研究领域需要大量当前不存在的数据&#xff0c;未来会受到这个限制 Alexandr Wang 强调了 AI 领域面临的数据问题。 他指出&#xff0c;前沿研究领域&#xff08;如多模态、多语言、专家链式思维和企业工作流&#xff09;需要大量当前不存在的数…

压缩能力登顶 小丸工具箱 V1.0 绿色便携版

平常录制视频或下载保存的视频时长往往都很长&#xff0c;很多时候都想要裁剪、 截取出一些“精华片段”保留下来&#xff0c;而不必保存一整个大型视频那么浪费硬盘空间… 但如今手机或电脑上大多数的视频剪辑软件&#xff0c;切割视频一般都要等待很长时间导出或转换&#…

【C语言回顾】编译和链接

前言1. 编译2. 链接结语 上期回顾: 【C语言回顾】文件操作 个人主页&#xff1a;C_GUIQU 归属专栏&#xff1a;【C语言学习】 前言 各位小伙伴大家好&#xff01;上期小编给大家讲解了C语言中的文件操作&#xff0c;接下来我们讲解一下编译和链接&#xff01; 1. 编译 预处理…

H5扫描二维码相关实现

H5 Web网页实现扫一扫识别解析二维码&#xff0c;就现在方法的npm包就能实现&#xff0c;在这个过程中使用过html5-qrcode 和 vue3-qr-reader。 1、html5-qrcode的使用 感觉html5-qrcode有点小坑&#xff0c;在使用的时候识别不成功还总是进入到错误回调中出现类似NotFoundExc…

天干物燥小心火烛-智慧消防可视化大屏,隐患防治于未然。

智慧消防可视化大屏通常包括以下内容&#xff1a; 1.实时监控&#xff1a; 显示消防设备、传感器、监控摄像头等设备的实时状态和数据&#xff0c;包括火灾报警、烟雾报警、温度报警等。 2.建筑结构&#xff1a; 显示建筑物的结构图和平面图&#xff0c;包括楼层分布、消防通…

VLC播放器(全称VideoLAN Client)

一、简介 VLC播放器&#xff08;全称VideoLAN Client&#xff09;是一款开源的多媒体播放器&#xff0c;由VideoLAN项目团队开发。它支持多种音视频格式&#xff0c;并能够在多种操作系统上运行&#xff0c;如Windows、Mac OS X、Linux、Android和iOS等。VLC播放器具备播放文件…

特殊变量笔记3

输入一个错误命令, 在输出$? 特殊变量&#xff1a;$$ 语法 $$含义 用于获取当前Shell环境的进程ID号 演示 查看当前Shell环境进程编号 ps -aux|grep bash输出 $$ 显示当前shell环境进程编号 小结 常用的特殊符号变量如下 特殊变量含义$n获取输入参数的$0, 获取当前She…

hugging face笔记:PEFT

1 介绍 PEFT (Parameter-Efficient Fine Tuning) 方法在微调时冻结预训练模型参数&#xff0c;并在其上添加少量可训练的参数&#xff08;称为适配器&#xff09;这些适配器被训练用来学习特定任务的信息。这种方法已被证明在内存效率和计算使用上非常高效&#xff0c;同时能产…

线性模型--普通最小二乘法

线性模型 一、模型介绍二、用于回归的线性模型2.1 线性回归&#xff08;普通最小二乘法&#xff09; 一、模型介绍 线性模型是在实践中广泛使用的一类模型&#xff0c;该模型利用输入特征的线性函数进行预测。 二、用于回归的线性模型 以下代码可以在一维wave数据集上学习参…

基于51单片机的超声波液位测量与控制系统

基于51单片机液位控制器 &#xff08;仿真&#xff0b;程序&#xff0b;原理图PCB&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.使用HC-SR04测量液位&#xff0c;LCD1602显示&#xff1b; 2.当水位高于设定上限的时候&#xff0c;对应声光报警报警&am…

【InternLM实战营第二期笔记】02:大模型全链路开源体系与趣味demo

文章目录 00 环境设置01 部署一个 chat 小模型02 Lagent 运行 InternLM2-chat-7B03 浦语灵笔2 第二节课程视频与文档&#xff1a; https://www.bilibili.com/video/BV1AH4y1H78d/ https://github.com/InternLM/Tutorial/blob/camp2/helloworld/hello_world.md 视频和文档内容基…