链接器工作原理

这篇具有很好参考价值的文章主要介绍了链接器工作原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

链接器解析符号

​ 链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来,可重定位目标文件的符号表在随笔ELF可重定位目标文件 - mjy66 - 博客园 (cnblogs.com)中有提到,以ELF格式的目标文件举例,.symtab节就是其符号表。

​ 在解析符号的过程中,编译器针对局部符号和全局符号有不同的规则。在解析局部符号的过程中,编译器只允许每个模块中每个局部符号有一个定义,而对于全局符号的解析,若遇到一个不是在当前模块中定义的符号,编译器会假设该符号是在其它模块中定义,生成链接器符号表条目,若后续链接器在任何输入模块中都找不到被引用的符号定义,则会报错。

1、链接器如何解析多重定义的全局符号

​ 链接器的输入是一组可重定位目标模块,每个模块有定义自己的一组符号,有些是局部(只对定义该符号的模块可见),有些是全局的(对其他模块也可见)。若多个目标模块定义了同名的全局符号,在Linux系统中,汇编器会以强符号或者弱符号来标记每个全局符号,函数和已初始化的全局变量为强符号,未初始化的全局变量为弱符号。Linux会根据以下规则处理多重定义的符号名:

  • 规则1:不允许有多个同名的强符号
  • 规则2:如果有一个强符号和多个弱符号同名,则选择强符号
  • 规则3:如果有多个弱符号同名,则从这些弱符号中任意选择一个。

例1:

//main.c
int x = 1000;

int main()
{
	return 0;
}

//f.c
int x = 1000;

void f()
{
}

​ main.c文件中定义并初始化了一个全局变量x,f.c文件中也定义并初始化了一个全局变量x,将这两个文件放在一起编译,会违反第一条规则,出现了两个同名的强符号,因此会出现如下报错。

链接器工作原理

例2:

//main.c
int x = 1000;

int main()
{
	printf("x = %d\n",x);
	return 0;
}

//f.c
int x;

void f()
{
}

​ 若不对f.c文件中x进行初始化,则f.c中的全局变量x变成了弱符号,此时根据规则2,会优先选择main.c文件中的强符号,因此编译之后,运行就会出现如下结果:

链接器工作原理

2、静态库的链接

​ 假设链接器不是读取一组可重定位目标文件,而是将所有相关的目标模块打包成一个单独的文件再作为链接器的输入,当链接器构造一个输出的可执行文件的时候,它只复制这个单独文件里被应用程序引用的目标模块,这个单独的文件就是静态库。静态库的出现能够在节省计算机内存的情况下,方便程序员调用相关函数。

​ 在linux系统中,静态库以存档的特殊文件格式存放在磁盘中,存档文件是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每个成员目标文件的大小和位置,存档文件名用后缀.a标识。

​ 实践能够让我们对知识点的理解更加深刻,接下来将使用AR工具创建一个自己简单的静态库。创建静态库的步骤如下:

​ 1、编写源文件

我们先创建三个分别对向量进行加减乘操作的文件,并在每个文件中记录被调用的次数。三个文件例程如下所示:

//addvec.c
int addcnt = 0;
void addvec(int *x,int *y,int n)
{
	int i;
	addcnt++;
	for(i = 0;i < n; i++)
		y[i] = x[i] + y[i];
}

//subvec.c
int subcnt = 0;
void subvec(int *x,int *y,int n)
{
    int i;
    subcnt++;
    for(i = 0;i < n; i++)
        y[i] = y[i] - x[i];
}

//multvec.c
int multcnt = 0;
int multvec(int *x,int *y,int n)
{
    int i;
    multcnt++;
    for(i = 0;i < n; i++)
        y[i] = y[i] * x[i];
    
}

​ 2、创建文件之后,在shell命令行中输入以下命令:

[root@master test]# gcc -c addvec.c subvec.c multvec.c
[root@master test]# ar rcs libvec.a addvec.o multvec.o subvec.o

​ 上述命令运行完成之后,便会输出libvec.a存档文件:

链接器工作原理

​ 3、编写一个应用程序来调用这个库里的函数,同时也要写一个声明静态库中函数或变量的头文件,应用程序如下:

#include <stdio.h>
#include “vector.h” //声明静态库里的函数和全局变量
int x[2]={1,2};
int y[2]={3,4};
int z[2]={5,6};

int main()
{
	addvec(x,y,2);
	printf("y = [%d %d]\n",y[0],y[1]);
	subvec(z,y,2);
	printf("y = [%d %d]\n",y[0],y[1]);
	multvec(y,y,2)
	printf("y = [%d %d]\n",y[0],y[1]);
	
	printf("addcnt = %d\n",addcnt);
	printf("subcnt = %d\n",subcnt);
	printf("multcnt = %d\n",multcnt);
	return 0;
}

​ 4、在编译的时候,链接时带上自己编写的库,在shell命令行中输入的命令如下:

[root@master test]# gcc main.c -L. -lvec
//-lvec参数是libvec.a的缩写
//-L.参数告诉链接器在当前的目录下查找libvec.a的缩写

​ 命令运行之后,会生成一个a.out文件,在命令行中输入运行该可执行文件,输出:

链接器工作原理

3、链接器如何使用静态库来解析引用

​ 在符号解析阶段,链接器会维护三个集合:

  • 集合E:储存可重定位目标文件,这些文件最终会被合并起来形成可执行文件
  • 集合U:储存未解析的符号,也就是引用了但是未定义的符号
  • 集合D:储存前面输入文件中已经定义的符号

​ 首先,命令行上的每个输入文件f,链接器会判断f是目标文件还是存档文件,如果f是目标文件,则将f添加到E,并通过修改U和D来反映f中的符号定义和引用。若f是存档文件,则链接器尝试匹配U中未解析的符号,若某个存档文件成员m定义了一个符号解析U中的一个引用,则将该存档文件成员加到E中,并根据该存档文件成员中的符号定义和引用来修改U和D,任何不包含在E中的成员目标文件都会被丢弃。循环反复以上过程,直到扫描完所有的输入文件,若最终U是非空的,则输出一个错误并终止,否则就合并E中的目标文件,构建输出的可执行文件。

​ 从上述过程中可以看出链接器对输入的文件的处理有个先后的过程,若在输入命令的时候不加注意,就会出现报错,假如将一个库文件放在调用该库的应用文件前,此时链接器先处理库文件,由于U中还是空的,因此直接跳过库文件,直接处理应用文件,显然应用文件中的符号不会得到匹配。

补充:

1、在生成自己的链接库的时候,按照CSAPP的命令行输入:

gcc -static -o prog main.c libvector.a

​ 会出现/usr/bin/ld:找不到-lc的报错。

​ 原因:在新版的Linux系统下安装gcc的时候,不会安装libc.a,只会安装libc.so,所以当加上-static选项时,找不到libc.a就报错找不到libc了

​ 解决方法:安装glibc-static

​ 参考链接:/usr/bin/ld: cannot find -lc错误原因及解决方法-CSDN博客

2、C++和Java中链接器如何区别重载函数之间的区别

​ 重载函数在源代码中都有相同的名字,但是有不同参数列表,编译器将每一个方法和参数列表编码成对链接器来说唯一的名字,这种编码的过程叫做重整。对类来说,重整的类名字是类名字中字符的整数数量+原始名字,比如类Foo被重整为3Foo。对方法来说,方法被编码为原始方法名+__+被重整的类名+每个参数的单字母编码,比如Foo::bar(int,long)被编码为bar__3Fooil文章来源地址https://www.toymoban.com/news/detail-710504.html

到了这里,关于链接器工作原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 全面解析大语言模型的工作原理

           当ChatGPT在去年秋天推出时,在科技行业乃至世界范围内引起了轰动。当时,机器学习研究人员尝试研发了多年的语言大模型(LLM),但普通大众并未十分关注,也没有意识到它们变得多强大。        如今,几乎每个人都听说过LLM,并有数千万人用过它们,但是,了

    2024年02月14日
    浏览(29)
  • 深入解析SDRAM:从工作原理到实际应用

    在众多内存技术中,同步动态随机访问存储器(SDRAM)因其出色的性能和广泛的应用而备受关注。本文将从SDRAM的工作原理入手,探讨其性能优化策略和在现代电子设备中的应用。 SDRAM利用同步技术提高数据处理效率,其独特之处在于能够与系统的时钟信号同步,从而优化数据

    2024年02月22日
    浏览(50)
  • 全面解析滑环的结构以及工作原理

    滑环的种类非常之多,初用着可能辉感到迷茫,今天小编就具体和大家聊聊滑环的分类以及结构和工作原理。  气滑环就是一类可以过真空,正压的360度旋转装置,也叫旋转接头,气滑环主要由主轴,外套,轴承,密封圈组装,通常情况下气压的工作参数是3~8Bar,转速从0-3

    2024年02月12日
    浏览(43)
  • Windows系统文件快捷链接(软链接/硬链接/符号链接)mklink

    记录学习mklink 将大文件从c盘移出 快捷方式 软链接 硬链接 符号链接 常见文件链接方式 在Windows上以*.lnk结尾的文件,这类文件通常用于指定某一个文件或某一个目录的位置,可扩展性很强, 桌面快捷方式 就是这类了。 说明 软链接A是一个虚拟文件夹,基本上不占用空间B是

    2024年02月03日
    浏览(55)
  • STM32 DMA的基本原理和工作机制解析

    STM32微控制器中的DMA(Direct Memory Access,直接内存访问)是一种用于高效数据传输的特殊硬件功能。 DMA允许外设之间直接进行数据传输,而无需CPU的干预。下面,我将为您详细解释STM32 DMA的基本原理和工作机制。 ✅作者简介:热爱科研的嵌入式开发者,修心和技术同步精进

    2024年02月20日
    浏览(49)
  • “深入解析JVM:揭秘Java虚拟机的工作原理“

    标题:深入解析JVM:揭秘Java虚拟机的工作原理 摘要:本文将深入解析Java虚拟机(JVM)的工作原理,探讨其内部结构和运行机制。我们将介绍JVM的组成部分、类加载过程、内存管理、垃圾回收、即时编译等关键概念,并通过示例代码展示JVM的运行过程。 JVM由三个主要组件组成

    2024年02月12日
    浏览(45)
  • “深入解析JVM:探索Java虚拟机的工作原理“

    标题:深入解析JVM:探索Java虚拟机的工作原理 摘要:本文将深入解析Java虚拟机(JVM)的工作原理,从字节码到执行过程,从内存模型到垃圾回收机制,逐步剖析JVM的核心组成部分和工作原理。通过本文的阅读,读者将对JVM有更深入的理解,并且能够运用这些知识来进行Jav

    2024年02月13日
    浏览(44)
  • “深入理解Java虚拟机(JVM):背后的工作原理解析“

    标题:深入理解Java虚拟机(JVM):背后的工作原理解析 摘要:本文将深入探讨Java虚拟机(JVM)的工作原理,包括内存管理、垃圾回收、即时编译器等关键概念,以及如何优化代码以提高性能。通过示例代码和详细解释,读者将对JVM的底层原理有更深入的理解。 正文: 一、

    2024年02月12日
    浏览(48)
  • 【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(下)

    在阅读了上篇文章《【深入挖掘Java技术】「源码原理体系」盲点问题解析之HashMap工作原理全揭秘(上)》之后,相信您对HashMap的基本原理和基础结构已经有了初步的认识。接下来,我们将进一步深入探索HashMap的源码,揭示其深层次的技术细节。通过这次解析,您将更深入地

    2024年02月01日
    浏览(47)
  • 【MySQL】MySQL体系结构与内部组件工作原理解析(原理篇)(MySQL专栏启动)

    📫作者简介: 小明java问道之路,专注于研究 Java/ Liunx内核/ C++及汇编/计算机底层原理/源码,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。         📫 热衷分享,喜欢原创~ 关注我会给你带来一些

    2024年01月15日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包