【C语言基础】:编译和链接(计算机中的翻译官)

news2025/6/21 0:56:24

文章目录

      • 一、翻译环境和运行环境
        • 1. 翻译环境
          • 1.1 编译
            • 1.1.1 预处理
            • 1.1.2 编译
            • 1.1.3 汇编
          • 1.2 链接
        • 2. 运行环境

一、翻译环境和运行环境

我们在Visual Studio上写的C语言代码其实都是一些文本信息,计算机是不能够直接执行他们的,计算机只能够执行二进制指令。
要想计算机执行我们所写的C语言代码,就需要一个"翻译官",将我们写的C语言代码"翻译"成计算机能够执行的二进制指令。而承当"翻译官"这个角色的就是我们常说的编译器

1. 翻译环境

ANSI C的任何⼀种实现中,存在两个不同的环境。

  1. 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令(二进制指令)。
  2. 第2种是执行环境,它用于实际执行代码。
1.1 编译

翻译环境是由编译链接两个大的过程组成的,而编译又可以分解成:预处理(有些书也叫预编译)、编译、汇编三个过程。
在这里插入图片描述

编译过程

  • 每个.c源文件都是独立地通过编译器进行编译处理的。编译器会将源代码转换为机器可以理解的中间形式,即目标代码。
  • 在Windows环境下,这些目标代码文件通常具有.obj扩展名;而在Linux环境下,目标文件的扩展名通常是.o。
  • 编译过程包括预处理、编译(语法分析、语义分析、代码生成等)和汇编(将汇编代码转换为机器代码)。

在这里插入图片描述
test.c生成test.objAdd.c生成Add.obj文件,每个C文件都会生成对应的目标文件,每个源文件都是经过编译器单独处理的。多个目标文件通过链接库生成我们的可执行程序。

如果在细分一点的话,编译又可以分解为预处理编译汇编三个过程。

在这里插入图片描述

1.1.1 预处理

在预处理阶段,源文件和头文件会被处理成为.i为后缀的文件。
它主要负责处理源代码中的预处理指令。预处理器是编译器的一个组成部分,它在编译器进行实际编译之前对源代码进行一系列的文本替换和宏替换操作。
在 gcc 环境下想观察⼀下,对 test.c 文件预处理后的.i文件,命令如下:

gcc test.c -E -o test.i

预处理阶段的主要任务包括:

  1. 宏替换(Macro Expansion)
  • 预处理器会处理所有的宏定义,将宏展开成它们所代表的代码。例如,如果定义了一个宏#define PI 3.14159,那么在预处理阶段,所有的PI宏在源代码中都会被替换成3.14159。
  1. 文件包含(File Inclusion)
  • 使用#include指令可以将其他文件的内容包含进来。预处理器会找到这些指定的头文件,并将它们的内容插入到当前文件的相应位置。这使得程序员可以重用代码,例如在多个文件中共享函数声明和类型定义。
  1. 条件编译(Conditional Compilation)
  • 预处理器还处理条件编译指令,如#ifdef、#ifndef、#if、#elif、#else和#endif。这些指令允许程序员根据特定的条件来包含或排除代码块,从而为不同的编译环境定制源代码。
  1. 移除注释(Comment Removal)
  • 预处理器会删除源代码中的注释,因为注释对于编译器来说是无意义的。注释以//或/* … */开始,直到行尾或注释块的结束。
  1. 添加编译器指令(Adding Compiler Directives)
  • 预处理器会添加一些特殊的编译器指令,如行号和文件名,这些信息对于调试程序非常有用。
    处理其他预处理指令:

预处理器还处理一些其他的指令,如#pragma,这些指令通常用于向编译器提供特定的、非标准的指令或请求。
预处理阶段的输出是一个已经经过上述处理的源代码文本,这个文本接下来会被送到编译器的下一阶段——编译阶段。在编译阶段,编译器将对预处理后的代码进行词法分析、语法分析、语义分析等操作,最终生成目标代码或汇编代码。

在这里插入图片描述
在这里插入图片描述
可以看到,生成的test.i文件里面有八百多行代码,我们写的代码才区区几行,前面的几百行代码都是stdio.h这个头文件里面的内容。另外,我们写的宏定义也直接被替换掉了。源代码中的注释也已经被删除。
所以注释是给程序员们看的,而不是给编译器看的。

1.1.2 编译

编译过程就是将预处理后的文件进行⼀系列的:词法分析语法分析语义分析及优化,生成相应的汇编代码文件。
编译过程的命令如下:

gcc test.i -S -o test.s

编译阶段的主要步骤:

以这段代码为例:

array[index] = (index+4)*(2+6);
  1. 词法分析(Lexical Analysis)
  • 编译器首先将预处理后的源代码进行词法分析,这一步骤涉及到将源代码字符串分解成一系列的记号(tokens)。记号是语言中最小的有意义的元素,如关键字、标识符、常量、运算符等。
  • 词法分析器通常会构建一个抽象的记号流,供后续阶段使用。

上面程序进行词法分析后得到了16个记号:
在这里插入图片描述

  1. 语法分析(Syntax Analysis)
  • 语法分析阶段,编译器根据C语言的语法规则检查记号流,构建一棵抽象语法树(AST)。这棵树表示了源代码的层次结构,反映了程序的逻辑组织。
  • 如果源代码不符合语言的语法规则,编译器将在这一阶段报告语法错误。

在这里插入图片描述

  1. 语义分析(Semantic Analysis)
  • 在语义分析阶段,编译器检查AST是否有意义,即检查语义正确性。这包括类型检查、变量声明的一致性、表达式的数据类型是否正确等。
  • 语义分析还会进行符号表的构建,记录变量、函数等的相关信息。

在这里插入图片描述

  1. 中间代码生成(Intermediate Code Generation)

通过上述分析后,编译器会生成中间代码,这种代码是一种独立于机器语言的低级代码,它更加接近于机器指令,但仍然保持了一定的抽象。
中间代码的设计旨在使得代码优化更加容易进行。

  1. 代码优化(Code Optimization)
  • 编译器会对中间代码进行优化,以提高代码的执行效率和减少资源消耗。优化可以在不同的层次进行,包括局部优化、循环优化、数据流分析等。
  • 优化的目标是减少代码的空间和时间复杂度,提高程序的性能。
  1. 目标代码生成(Code Generation)
  • 最后,编译器将优化后的中间代码转换成目标代码,即可以直接在特定硬件和操作系统上执行的机器代码或汇编代码。
  • 目标代码生成阶段需要考虑目标平台的具体指令集和调用约定。

编译阶段是一个复杂的过程,涉及到对源代码的深入理解和转换。编译器的设计和实现需要考虑到语言的特性、目标平台的特点以及程序的性能要求。通过编译阶段,高质量的源代码被转换成有效的机器指令,为最终的程序执行奠定了基础。

在这里插入图片描述

1.1.3 汇编

汇编器是将汇编代码转转变成机器可执行的指令,每⼀个汇编语句几乎都对应一条机器指令。就是根据汇编指令和机器指令的对照表一一的进行翻译,也不做指令优化。

汇编的命令如下:

gcc test.s -c -o test.o
  1. 汇编指令
  • 汇编指令是针对计算机硬件的低级指令,它们通常与机器代码一一对应,但是以一种更易于人类理解和编写的形式表示。
  • 汇编指令包括操作码(opcode)和操作数(operands),操作码指定要执行的操作,操作数提供操作所需的数据或地址。
  1. 地址和数据
  • 汇编器负责将汇编指令中的地址和数据转换为计算机可识别的二进制形式。
  • 这包括对内存地址、寄存器、立即数等的处理和转换。
  1. 符号解析
  • 在汇编代码中,可能会使用标签(labels)和符号(symbols)来引用内存位置或数据。汇编器将这些符号解析为具体的地址或值。
  • 例如,一个标签可能代表一个内存地址,汇编器需要确保所有对该标签的引用都被正确地转换为对应的地址。
  1. 目标文件生成
  • 汇编器处理完所有的汇编指令后,会生成一个目标文件(Object File)。目标文件包含了机器代码和与链接器(Linker)相关的符号信息。
  • 目标文件通常具有特定的格式,如在Windows上通常是.obj文件,在Unix-like系统上通常是.o文件。
  1. 代码优化
  • 虽然主要的优化工作在编译阶段进行,但汇编器也可以执行一些简单的优化,比如消除冗余的指令或改善指令的顺序以提高执行效率。
  1. 依赖处理
  • 汇编器还需要处理源文件中对外部符号的依赖,这些外部符号可能定义在其他汇编源文件或库文件中。
  • 汇编器记录这些依赖关系,并在链接阶段由链接器解决。

在这里插入图片描述

1.2 链接

链接是编译过程的最后一个阶段,它负责将编译阶段生成的一个或多个目标文件与所需的库文件合并,生成最终的可执行文件。链接过程由链接器(Linker)完成,它解决了目标文件之间的相互引用和依赖问题,确保程序中的所有函数和变量引用都能正确地指向它们的实现和定义。

  1. 符号解析(Symbol Resolution)
  • 链接器处理程序中的符号,如函数和全局变量。每个符号都有一个唯一的名称,链接器需要确保每个符号引用都能正确地找到其对应的定义。
  • 当一个目标文件引用了另一个目标文件中的符号时,链接器会找到该符号的定义,并在链接时进行适当的修改。
  1. 地址分配和重定位(Address Assignment and Relocation)
  • 链接器为程序中的所有代码和数据分配内存地址。这个过程涉及到确定每个符号和数据在内存中的确切位置。
  • 重定位是链接过程中的一个关键步骤,它涉及到修改代码中的地址引用,确保它们指向正确的内存位置。这是因为在编译时,编译器并不知道最终的内存布局。
  1. 处理静态和动态库(Static and Dynamic Libraries)
  • 静态库在链接阶段被整合到最终的可执行文件中,成为程序的一部分。这意味着程序运行时不再需要这些库的单独文件。
  • 动态库(或共享库)在程序运行时被加载。它们可以在多个程序之间共享,节省内存和磁盘空间。链接器在链接动态库时,会记录库的路径和所需的符号,以便在运行时找到它们。
  1. 生成可执行文件(Generating the Executable File)
  • 链接器完成所有必要的链接工作后,会生成一个可执行文件。这个文件包含了程序的所有代码、数据、符号表、以及运行时所需的其他信息。
  • 可执行文件的格式依赖于目标操作系统和平台。例如,在Windows上通常是.exe文件,在Linux上通常是没有扩展名的文件。
  1. 处理链接时错误(Link-Time Errors)
  • 如果在链接过程中发现错误,如未定义的符号、多重定义、或者不兼容的库版本,链接器会报告这些错误。程序员需要根据错误信息对代码进行修正,然后重新编译和链接。

【示例】
test.c

#include<stdio.h>
//test.c
//声明外部函数
extern int Add(int, int);
//声明外部的全局变量
extern int g_val;
int main()
{
	int a = 10;
	int b = 20;
	int c = Add(a, b);
	printf("%d\n", c);
	return 0;
}

Add.c

int g_val = 2022;
int Add(int x, int y)
{
	return x + y;
}

在这里插入图片描述
我们已经知道,每个源文件都是单独经过编译器处理生成对应的目标文件。
test.c 经过编译器处理生成 test.o
add.c 经过编译器处理生成 add.o
我们在 test.c 的文件中使用了 add.c 文件中的 Add 函数和 g_val 变量。
我们在 test.c 文件中每⼀次使用 Add 函数和 g_val 的时候必须确切的知道 Add 和 g_val 的地址,但是由于每个文件是单独编译的,在编译器编译 test.c 的时候并不知道 Add 函数和 g_val变量的地址,所以暂时把调用 Add 的指令的目标地址和 g_val 的地址搁置。等待最后链接的时候由链接器根据引用的符号 Add 在其他模块中查找 Add 函数的地址,然后将 test.c 中所有引用到Add 的指令重新修正,让他们的目标地址为真正的 Add 函数的地址,对于全局变量 g_val 也是类似的方法来修正地址。这个地址修正的过程也被叫做:重定位

2. 运行环境
  1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
  2. 程序的执行便开始。接着便调用main函数。
  3. 开始执行程序代码。这个时候程序将使用⼀个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程⼀直保留他们的值。
  4. 终止程序。正常终止main函数;也有可能是意外终止。

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

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

相关文章

机器人方向控制中应用的磁阻角度传感芯片

磁阻传感器提供的输出信号几乎不受磁场变动、磁温度系数、磁传感器距离与位置变动影响&#xff0c;可以达到高准确度与高效能&#xff0c;因此相当适合各种要求严格的车用电子与工业控制的应用。所以它远比采用其它传感方法的器件更具有优势。 机器人的应用日渐广泛&#xff0…

linux-docker删除redis容器

1、查看已经安装的redis镜像 docker ps2、通过别名或者通过容器ID&#xff1a;docker stop name/id docker stop 019814493c7a # id停止3、删除容器&#xff1a;可以通过name或id docker rm 019814493c7a # 通过id删除容器4、删除镜像&#xff1a; docker images # …

缓存策略以及如何选择正确的策略

正确地使用缓存可以减少系统响应的时间&#xff0c;降低数据库负载&#xff0c;反之可能带来相反的效果。因此&#xff0c;就必须了解有哪些缓存策略&#xff0c;以及如何根据实际使用场景选择合适的缓存策略。 缓存策略取决于数据和数据访问模式&#xff0c;即&#xff0c;数…

DSP笔记10-CPU定时器

28335有三个CPU定时器 高位寄存器H CPU定时器工作原理 周期寄存器PRDH:PRD 计数器寄存器TIMH:TIM 周期寄存器PRDH:PRD赋值后装在到计数器寄存器TIMH:TIM&#xff0c;TIMCLK&#xff08;脉冲&#xff09;减1&#xff0c;直至为0。 TIMCLK&#xff08;脉冲&#xff09;通过定…

yolov8多分支任务头训练

目前已知的yolov8可以针对多个任务进行单独训练,但是暂时还没有开放针对多个任务头同时进行训练的教程,本文章针对yolov8的多任务训练进行详细介绍。 先放上效果图: 三个任务,分别是目标检测、可行驶区域、车道线,具体步骤请往下看。 一、环境配置 从如下github下载代码…

idm线程越多越好吗 idm线程数多少合适 IDM百度云下载 IDM下载器如何修改线程数

IDM&#xff08;Internet Download Manager&#xff09;是一款流行的网络下载器&#xff0c;它支持多线程下载&#xff0c;这意味着它可以同时建立多个连接来下载文件的不同部分&#xff0c;从而提高下载速度。我们在使用IDM的时候总是有很多疑问&#xff0c;今天我们学习IDM线…

【科学计量】剔除来自unknown的机构与作者文献文献——数据清洗

剔除来自unknown的机构与作者文献文献——数据清洗 背景实例解决方法方法一: 使用专门处理bibx文件的库方法二: 直接处理纯文本数据背景 有时在研究过程中,会遇到不同类型的文献,但是有些文献中的数据会有部分缺失,常见的比如机构,作者和年份等字段,因此为了使用科研工…

esxi配置使用以及虚拟机管理

vSphere Client安装和esxi主机 esxi配置 许可证 虚拟机管理 vSphere Client中创建、删除等虚拟机的管理。 vSphere Client中创建虚拟机并安装操作系统&#xff1b; 步骤1 创建虚拟机 密码&#xff1a;Tongxin2023 (1) 厚置备延迟置零 以默认的厚格式创建虚拟磁盘。创建过…

机器学习实现文本分类

传统的向量空间模型&#xff08;VSM&#xff09;假设特征项之间相互独立&#xff0c;这与实际情况是不相符的&#xff0c;为了解决这个问题&#xff0c;可以采用文本的分布式表示方式(例如 word embedding形式)&#xff0c;通过文本的分布式表示&#xff0c;把文本表示成类似图…

使用API有效率地管理Dynadot域名,重新提交域名转移密码

关于Dynadot Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮…

通用外设-红外遥控器(NEC协议)

目录 前言 一、前期的准备 1. 结构体 2. 中断 1.定时中断 2.外部中断&#xff08;下降沿中断&#xff09; 二、功能实现 1.时序说明 2.逻辑实现 3.代码实现 总结 前言 简单介绍红外遥控器的使用&#xff0c;可以正常使用&#xff0c;但是部分功能未启用&#xff0c;…

风险评估在应对网络安全威胁中扮演着重要的角色

如今&#xff0c;IT 安全专家面临各种重大威胁&#xff0c;从勒索软件、网络钓鱼&#xff0c;到对基础设施的攻击&#xff0c;再到对知识产权、客户数据的窃取&#xff1b;从不安全的供应链合作伙伴&#xff0c;再到组织内部人员的恶意行为。同时&#xff0c;随着云计算、远程工…

关于ASP.NET Core WebSocket实现集群的思考

前言 提到WebSocket相信大家都听说过&#xff0c;它的初衷是为了解决客户端浏览器与服务端进行双向通信&#xff0c;是在单个TCP连接上进行全双工通讯的协议。在没有WebSocket之前只能通过浏览器到服务端的请求应答模式比如轮询&#xff0c;来实现服务端的变更响应到客户端&…

【鹅厂摸鱼日记(二)】(生活篇)初到深圳的人情冷暖

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:重生之我在鹅厂摸鱼⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多知识   &#x1f51d;&#x1f51d; 摸鱼日记 1. 前言2. 鹅厂的人文关怀…

FFmpeg: 简易ijkplayer播放器实现--03UI界面设计

文章目录 UI设计流程图UI设计界面点击播放功能实现 UI设计流程图 UI设计界面 主界面 控制条 播放列表 画面显示 标题栏 设置界面 提示框 点击播放功能实现 槽函数实现&#xff1a; connect(ui->ctrlBarWind, &CtrlBar::SigPlayOrPause, this, &Main…

09 Php学习:数组和排序

数组概念 在PHP中&#xff0c;数组是一种复合数据类型&#xff0c;用于存储多个值。以下是关于PHP数组的详细解释&#xff1a; 索引数组&#xff1a;索引数组是最基本的数组类型&#xff0c;其中每个元素都有一个唯一的数字索引&#xff0c;从0开始递增。 关联数组&#xff…

1、Qt UI控件 -- qucsdk

前言&#xff1a;Qt编写的自定义控件插件的sdk集合&#xff0c;包括了各个操作系统的动态库文件以及控件的头文件和sdk使用demo。类似于Wpf中的LivChart2控件库&#xff0c;都是一些编译好的控件&#xff0c;可以直接集成到项目中。该控件是飞扬青云大神多年前开发的&#xff0…

算法刷题应用知识补充--搜索与图

这里写目录标题 DFS题结 BFS题结 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 DFS 题 知识点1&#xff1a;本题在dfs的for循环搜索中&#xff0c;进行了剪枝&#xff0c;即写一个判断函数&#xff0c;把不符合题意的…

PHP Storm 2024.1使用

本文讲的是phpstorm 2024.1最新版本激活使用教程&#xff0c;本教程适用于windows操作系统。 1.先去idea官网下载phpstorm包&#xff0c;我这里以2023.2最新版本为例 官网地址&#xff1a;https://www.jetbrains.com/zh-cn/phpstorm/ 2.下载下来后安装&#xff0c;点下一步 …

【数据结构与算法】搜索算法(深度优先搜索 DFS和广度优先搜索 BFS)以及典型算法例题

目录 搜索算法&#xff08;深度优先搜索DFS和广度优先搜索BFS&#xff09;以及典型算法例题深度优先搜索 &#xff08;Depth First Search 简称 DFS&#xff09;DFS 的设计步骤深度优先搜索&#xff08;DFS&#xff09;算法例题例题一&#xff1a;N皇后问题例题二&#xff1a;路…