C++:Article:链接器(二):符号决议

news2025/1/18 7:04:29

链接器

  • 1. C++源文件都有些什么
    • 1.1 . 目标文件里有什么
  • 2. 符号表 Symbol table
    • 2.1. 符号表的位置
    • 2.2. 符号的决议
    • 2.3. 符号决议过程
  • 3. 实例说明
    • 3.1. 意外出现
    • 3.2 总结排查

在上篇文章中,我们介绍了 链接器基本概念,我们知道所有的应用程序否是连接器将所需要的一个个简单的目标文件汇集起来形成的。
比如:我们在 list 中实现了一种特定的链表数据结构,其他模块需要使用这种链表,这就是模块直接的依赖。

  1. 确保目标文件能找到符号定义 (Symbol Resolution) 符号决议
    🚀 链接器的其中一项重要任务就是 确保提供链接器进行链接的目标文件集合之间依赖是成立的(也就是说不会出现在被依赖的模块中链接器找不到需要依赖的接口)。

  2. 可执行程序的生成
    🚀 链接器会首先将程序每个模块当中目标文件集合链接成库,然后在将各个库进行链接最终形成可执行程序。

  3. 重定位
    🚀 在完成 符号决议和生成可执行文件之后,链接器需要对可执行文件进行重定位。

下面我们围绕上面三个部分来详细的讲解下每一个过程。

1. C++源文件都有些什么

一个典型的C++源文件中,该文件中的变量可以划分为两类。

  • 全局变量:只要程序没结束运行,全局变量都可以随时使用。(注意:静态的全局变量生命周期也等于程序的运行周期,只是这种全局变量只能在所 被定义的文件当中使用,对其它文件不可见)。
  • 局部变量:局部变量的生命周期和全局变量的生命周期不同,局部变量只能在相应的函数内部使用,当函数调用完毕后,局部变量也不能使用 。
// 1. 定义未初始化的全局变量
int x_global_uninit;
// 2. 定义初始化的全局变量
int x_global_init = 1;
// 3. 定义未初始化的全局私有变量,该变量只能在当前文件中使用
static int y_global_unint;
// 4. 定义未初始化的全局私有变量,该变量只能在当前文件中使用
static int y_global_init = 2;
// 5. 声明全局变量,但是该变量在其他文件中定义
extern int z_global;
// 6. 声明函数,该函数在其他文件中定义
int fn_a(int x, int y);
// 7. 函数定义,因为使用static 修饰,该函数只能在当前文件中使用
static int fn_b(int x)
{
	return x+1;
}
// 8. 函数定义,该函数可以被其他文件使用
int fn_c(int x_local)
{
	// 9. 未初始化的局部变量
	int y_local_unint;
	// 10. 已初始化的局部变量
	int y_local_init = 3;
	// 11. 全局变量,局部变量以及函数的使用
	x_global_uninit = fn_a(x_local,x_global_init);
	y_local_uninit = fn_a(x_local,y_local_init);
	y_local_uninit +=  fn_b(z_global);
	return (y_global_uninit+ y_local_uninit);
}

1.1 . 目标文件里有什么

编译器的任务就是把人类可以理解的代码 转换成机器可以执行的机器指令,源文件编译后形成对应的目标文件本质上可以分成两部分

  1. 代码部分:计算机可以执行的机器指令,也就是源文件中定义的所有函数,比如上图中:fn_a() fn_b() 等
  2. 数据部分:源文件中定义的全局变量,如果是已经初始化后的全局变量,该全局变量的值也存在于数据部分。

2. 符号表 Symbol table

编译器在编译过程中遇到外部定义的全局变量或函数时,只要能在当前文件中找到其声明即可,编译器就会认为是正确的。
寻找变量的定义就被留给了 链接器,链接器的一项重要任务就是要确定所使用的变量要有其唯一的定义。虽然这项工作留给了链接器,但是为了让链接器工作的轻松一点,编译器还是多做了一点工作,这部分工作就是 符号表(Symbol table)。 那么符号表保存的是什么了 ?

🚀🚀🚀

  1. 该目标文件中引用的全局变量以及函数
  2. 该目标文件中定义的全局变量以及函数
    如 标题2所知,编译器在编译过程中,每次遇到一个全局变量或者函数名都会在符号表中添加一项,最终编译器会统计出如下所示的一张读好表

在这里插入图片描述

2.1. 符号表的位置

符号表被编译器很贴心的放在目标文件中,因此一个目标 文件可以理解为下图所示的三段
在这里插入图片描述

2.2. 符号的决议

在上一节符号表中,我们知道符号表给链接器提供了两种信息。

  1. 一个是当前目标文件可以提供给其他目标文件使用的符号。
  2. 另一个是其他目标文件需要提供给当前目标文件使用的符号。

2.3. 符号决议过程

如下图所示,假设链接器需要链接下面三个文件。链接器会一次扫描每一个给定的目标文件,同时链接器还维护了两个集合,一个是已定义符号集合D,另一个是未定义的集合U,下面是链接器进程符号决议的过程:
在这里插入图片描述

  1. 对于当前目标文件,查找其符号表,并将已定义的符号并添加到已定义符号集合D中。
  2. 对于当前目标文件,查找其符号表,将每一个当前目标文件引用的符号与已定义符号集合D进行对比,如果该符号不在集合D中则将其添加到未定义符号集合U中。
  3. 当所有文件都扫描完成,如果未定义符号集合U不为空,则说明当前输入的目标文件中有未定义符号错误,链接器就会报错,整个编译过程就会终止。

3. 实例说明

// 伪代码
// math.h
#include<iostream>
int add(int a, int b);

// math.cpp
#include<iostream>
int add(int a, int b)
{
	return a+b;
}
// main.cpp
#include<iostream>
#include "math.h"
int main()
{
	int sum = add(1,2);
	std::cout << sum << std::endl;
	return 0;
}

链接过程如下:
编译器在 链接 main.o 和 math.o 文件时。

  1. matth.o 目标文件有add() 函数符号,首先会在当前文件查找定义,结果当前文件就存在add() 函数定义,所以直接将符号add 添加到已定义集合D中
  2. main.o 目标文件也有add() 函数符号,首先会在当前文件中查找定义,结果当前文件不存在add() 函数定义,然后在集合D中查找是否有定义,结果找到了符号的定义
  3. 当完成 main.o 和 math.o 两个目标文件链接后,编译终止,生成可执行文件。

在这里插入图片描述

3.1. 意外出现

假设你不小心将 math.cpp 中的add函数注释了,但是main.cpp 仍然引用了add() 函数符号,当你在编译的时候,就会报很经典的
undefined reference to add(int, int) 错误。
在这里插入图片描述

现在我们来分析下产生这个错误的原因。

  • 编译器发现你写的代码 main.o 中引用了外部定义定义的函数(通过检查目标文件 main.o 中的符号表得到的信息),所以链接器开始寻址这个add()符号到底是在哪里定义的。
  • 链接器先去目标文件 main.o 的符号表中查找,没有找到 add() 符号的定义。
  • 转而链接器去其他目标文件符号表中查找,通用没有找到add() 函数符号的定义
  • 链接器在查找了所有目标文件的符号表后都没找到add() 函数符号,因此链接器停止工作并报出 undefined reference to add(int, int)

3.2 总结排查

所以根据前面几节的介绍讲解,你已经很清楚的知道链接器符号决议整个过程,当出现 未定义符号错误时,你可以进行如下排查

  1. main.cpp 中对add函数的函数名是否书写正确
  2. 链接命令中是否包含了 math.o ,如果没有,那么需要添加上该目标文件
  3. 如果链接命令没有问题,查看 math.cpp 中关于 add函数的定义是否存在问题
  4. 如果是C和C++的混合编程,确保相应的位置添加 extern C
    一般情况下,经过这几个步骤的排查,基本能解决上述问题

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

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

相关文章

基于Python长时间序列遥感数据处理及在全球变化、物候提取、植被变绿与固碳分析、生物量估算与趋势分析

植被是陆地生态系统中最重要的组分之一&#xff0c;也是对气候变化最敏感的组分&#xff0c;其在全球变化过程中起着重要作用&#xff0c;能够指示自然环境中的大气、水、土壤等成分的变化&#xff0c;其年际和季节性变化可以作为地球气候变化的重要指标。此外&#xff0c;由于…

Cesium基础教程

一、概述 Cesium是国外一个基于javascript的地图引擎&#xff0c;支持3D、2D、2.5D形式的展示&#xff0c;可以自行绘制图形、高亮区域&#xff0c;并提供良好的触摸支持&#xff0c;并支持大多数的浏览器和移动端。 Cesium 是一个跨平台、跨浏览器的展示三维地球和地图的 ja…

【PWN刷题__ret2text】[CISCN 2019华北]PWN1

ret2text~ 前言 依旧是简单的ret2text 一、checksec查看 No canary found 没有开启栈溢出保护 二、IDA反汇编 双击进入func() 发现后门函数system("cat/flag")&#xff1b;根据语义&#xff0c;函数提供了修改v1&#xff0c;判断v2是否等于11.28125&#xff0c;如…

【倒计时4天】金融服务用户体验专场沙龙开启预约

易观&#xff1a;数智化浪潮下&#xff0c;金融业务用户运营从线下向全渠道延伸。用户体验已成为⾦融机构打造差异化的重要抓手。此外&#xff0c;随着⽤户体验的全⾯数字化和精细化&#xff0c;⽤户体验贯穿于整个客户旅程中&#xff0c;做好用户体验管理&#xff0c;体现差异…

okio篇 1.Buffer

总览: Okio的两个基本概念&#xff1a;Source和Sink。Source对标基础io库中的InputStream&#xff0c;负责读数据。Sink对标OutputStream&#xff0c;负责写数据。 Source和Sink的内部实现&#xff0c;都是一个Buffer。Buffer从字面意思理解就是一个缓冲区&#xff0c;跟Buff…

『pyqt5 从0基础开始项目实战』12. 实现多线程循环检测数据的开始和停止(保姆级图文)

目录 最终效果导包和框架代码 main.py避免重复执行开始与停止事件表格更新事件 scheduler.py初始化开始线程停止线程在线程列表中删除线程TaskThread新建线程StopThread停止线程完整代码main.pythreads.pyscheduler.py 总结 欢迎关注 『pyqt5 从0基础开始项目实战』 专栏&#…

C. Anadi and Domino(思维 + 枚举)

Problem - C - Codeforces 阿纳迪有一副多米诺骨牌。每张多米诺骨牌都有两个部分&#xff0c;每个部分都包含一些小点。对于每一个a和b&#xff0c;如1≤a≤b≤6&#xff0c;1≤a≤b≤6&#xff0c;正好有一块多米诺骨牌的一半是a点&#xff0c;另一半是b点。这组多米诺骨牌正好…

手动实现 Tomcat 底层机制+ 自己设Servlet

目录 手动实现 Tomcat 底层机制 自己设Servlet 完成小案例 运行效果 此项目用maven至于怎么配置在下一篇文章 创建cal.html CalServlet.java web.xml WebUtils 问题: Tomcat 整体架构分析 测试分析&#xff1a; 抓包情况 手动实现 Tomcat 底层机制 自己设计 Serv…

十个高质量工具网站推荐,AI自动抠图换背景,任意背景自动融合

AI 背景更换是一种利用生成式人工智能创建新图像背景的软件工具。与传统方法需要移除原有的背景并更换新的不同&#xff0c;AI背景生成器使用先进的算法生成与前景完美融合的全新背景。这项技术彻底改变了图像编辑的方式&#xff0c;为设计提供了更多的创造自由和灵活性。 特点…

数据结构--B树、B+树

数据结构--B树、B树 1. 什么是B树2.建立B树的要求3.什么是B树4.Mysql里面为什么使用B树作为索引结构&#xff1f; 1. 什么是B树 B树是一种数据结构&#xff0c;用于在硬盘或其他非易失性存储介质上快速存储和访问大量数据。它是一种平衡树&#xff0c;其每个节点可以存储多个键…

zabbix SNMP traps 监控案例

目标 根据H3C网络设备 发送 SNMP trap 信息进行网络端口的告警。 具体过程 继上次配置的trap 方式进行监控一个案例。 其中log数据中的内容是&#xff1a; 20230330.163810 ZBXTRAP 192.168.21.148 UDP: [192.168.21.148]:52289->[172.18.18.2]:1162 DISMAN-EVENT-MIB::…

Keil5软件安装方法(兼容stm32与c51方法)

目录 一、下载软件包 二、安装软件 1、安装C51v960a.exe (1&#xff09;右键以管理员权限运行程序 &#xff08;2&#xff09;开始安装软件 &#xff08;3&#xff09;勾选协议 &#xff08;4&#xff09;选择安装路径 &#xff08;5&#xff09;填写名字与邮箱 &#xff0…

我国元宇宙行业分析:政策、技术、资金助推行业探索多元化应用场景

1.元宇宙行业概述、特征及产业链图解 元宇宙是人类运用数字技术构建的&#xff0c;由现实世界映射或超越现实世界&#xff0c;可与现实世界交互的虚拟世界&#xff0c;具备新型社会体系的数字生活空间&#xff0c;主要具有沉浸式体验、开放性、虚拟身份、不断演化、知识互动、…

c/c++:指针,指针定义和使用,指针大小4字节,野指针,空指针*p=NULL

c/c:指针&#xff0c;指针定义和使用&#xff0c;指针大小4字节&#xff0c;野指针&#xff0c;空指针*pNULL 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;此时学会c的话&#xff0c; 我所知道的周边的会c的同学&#xf…

VMware开机自启虚拟机系统

一、前提 wmware开机自启&#xff0c;安装完毕wmware不用管&#xff0c;默认该软件以及相关服务就是开机自启准备waware虚拟机&#xff08;一般都linux&#xff0c;我用centos7&#xff0c;你随意&#xff09; 二、脚本 脚本命令如下&#xff0c;等待30秒&#xff08;给服务自启…

NXP公司K20+PF8100实现硬件窗口看门狗

Kinetis K20 72 MHz MCU系列为中等性能的Kinetis产品组合提供了可扩展的入门级产品&#xff0c;具有差异化的集成&#xff0c;配备高精度模拟集成和灵活的低功耗功能。其相关资源可在NXP的官网获得。 PF81/PF82为PMIC系列专为高性能处理应用而设计&#xff0c;如娱乐中控、车载…

阅读完synchronized和ReentrantLock的源码后,我竟发现其完全相似

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小黄&#xff0c;独角兽企业的Java开发工程师&#xff0c;CSDN博客专家&#xff0c;阿里云专家博主&#x1f4d5;系列专栏&#xff1a;Java设计模式、数据结构和算法、Kafka从入门到成神、Kafka从成神到升仙…

【数据结构】七大排序之快速排序详解(挖坑法快排,非递归快排,二路快排,三路快排)

目录 1.快速排序核心思路 2.挖坑法快速排序&#xff08;递归&#xff09; 2.1步骤 2.2代码&#xff08;详细注释&#xff09; 3.非递归快排&#xff08;用栈实现快速排序&#xff09; 3.1思路 3.2代码 4.二路快排 4.1思路 4.2代码 5.三路快排 5.1思路 5.2代码 1.快速…

大白话chatGPT及其原理之快速理解篇

大白话chatGPT及其原理之快速理解篇 从GPT名字理解chatGPTchatGPT三步曲 声明&#xff1a;本文为原创&#xff0c;未经同意请勿转载&#xff0c;感谢配合&#x1f604; chatGPT今年年初的时候是非常火爆的&#xff0c;现在也有很多相关的应用和插件。当然现在也有很多新的技术出…

老宋 带你五分钟搞懂vue

Vue 1.1 什么是框架 任何编程语言在最初的时候都是没有框架的&#xff0c;后来随着在实际开发过程中不断总结『经验』&#xff0c;积累『最佳实践』&#xff0c;慢慢的人们发现很多『特定场景』下的『特定问题』总是可以『套用固定解决方案』。于是有人把成熟的『固定解决方案…