C 语言编译链接

news2024/11/18 1:30:07

前言

一个 C 程序究竟是怎么变成可执行程序的,这其间发生了什么?本文将带你简要了解 C 程序编译过程,文章为 《程序员的自我修养—链接、装载与库》的读书笔记,更为详细的过程可以阅读原书。

比如下面一个经典的 C 程序,它可以用来测试我们开发环境是否配置正确,那它经历了什么?

// hello.c 
#include <stdio.h>

int main() {
	printf("hello world!\n");
    return 0;
}

在 Linux 下,我们使用 GCC 来编译该程序只需如下命令:

$ gcc hello.c
$ ./a.out
hello world!

实际上,上述过程可以分为 4 个步骤,分别是预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。

compilation process

预处理

首先是源代码文件 hello.c 和相关头文件,如 stdio.h 等被预处理器 cpp 预处理成一个 .i 文件。

想得到预处理后的文件可以使用如下命令(-E 表示预处理后便停止):

$ gcc -E hello.c -o hello.i

预处理过程主要工作是处理源代码中以 # 开头的预处理指令。比如 #include、#define 等,主要处理规则如下:

  • 删除所有的注释
  • 替换所有的宏定义,并将所有的 #define 删除
  • 添加行号及文件名标识
    • 以便编译时产生编译错误或警告时能显示行号
    • 编译时编译器产生调试用的行号信息
  • 处理条件编译指令,如 #ifdef、#endif
  • 处理 #include 指令,将被包含的文件插入到该预处理指令的位置
    • 这个过程是递归进行的,因为被包含的文件可能还包含其他文件
    • #include <filename>,将根据相应规则查找该文件
      • 就是在编译器定义的一系列标准位置查找函数库头文件
      • Linux 的 C 编译器在 /usr/include 目录查找函数库头文件
    • #include “filename”,将在源代码所在位置查找该文件
      • 如果失败,编译器再按照函数库头文件的处理方式对它们处理
  • 保留 #pragam 编译器指令,该语句在编译时使用

经过预处理后的 .i 文件不包含任何宏定义,所有的宏已经被展开了,包含的头文件也已经被插入了。

编译

编译过程就是把预处理完的文件进行一系列的词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件。

想得到编译后的文件可以使用如下命令(-S 表示编译后便停止):

$ gcc -S hello.c -o hello.s

编译过程一般可以分为 6 步:扫描、语法分析、语义分析、源代码优化、代码生成和目标代码优化。

nd compilation

下面以一段简单的 C 语言代码来介绍这个过程:

array[index] = (index + 4) * (2 + 6);

词法分析

首先源代码程序被输入到扫描器(Scanner),它简单地进行词法分析,将源代码的字符序列分隔成一系列的记号(Token)。

上述代码经过扫描后,产生的记号如下:

记号类型
array标识符
[左方括号
index标识符
]右方括号
=赋值
(左圆括号
index标识符
+加号
4数字
)右圆括号
*乘号
(左圆括号
2数字
+加号
6数字
)右圆括号

词法分析产生的记号大致可分为:关键字、标识符、字面值(数字、字符串等)和特殊符号(如加号、减号)。在标识记号的同时,扫描器将标识符放到符号表,将数字、字符串常量存放到文字表。

语法分析

接下来语法分析器(Grammar Parser)将对由扫描器产生的记号进行语法分析,从而产生语法树(Syntax Tree)。由语法分析器生成的语法树就是以表达式(Expression)为节点的树。

上述记号经语法分析器以后形成如下的语法树:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wISRmb6L-1673746047276)(https://qin1230.oss-cn-shenzhen.aliyuncs.com/boke/Syntax%20Tree.jpg)]

整个语句被看作是一个赋值表达式,赋值表达式左边是一个数组表达式,它的右边是一个乘法表达式,等等。

在此阶段如果出现了表达式不合法,比如括号不匹配、表达式中缺少操作符等,编译器就会报告语法分析阶段的错误。

语义分析

接下来进行语义分析,由语义分析器(Semantic Analyzer)来完成。语法分析器仅仅是完成了对表达式的语法层面的分析,但它并不了解这个语句是否有意义。比如 C 语言中两个指针做加法或乘法运算是没有意义的,但这个语句在语法上是合法的。编译器所能分析的语义是静态语义,是指在编译期就可以确定的语义,与之对应的是动态语义只有在运行期才能确定。

静态语义通常包含声明和实际类型的匹配,类型的转换是否可以进行。动态语义一般指在运行期出现的语义相关的问题,比如除 0 错误。

经过语义分析阶段以后,整个语法树都被标识了类型,如果有类型需要类型转换,语义分析会在语法树插入相应的转换节点。

上述语法树经过语义分析后形成下图:

nd Syntax Tree

源代码优化

现在的编译器有着很多层次的优化,往往在源代码级别会有一个优化过程。

源代码优化器(Source Code Optimizer)会在源代码级别进行优化,在上例中,(2 + 6)这个表达式可以被优化掉,因为它的值在编译期就可以被确定。

还有一个常见的源代码级别的优化如下:

// 此处的 i++ 会被优化成 ++i
// 因为 i++ 会产生一个临时变量,此处也用不到该临时变量
// 优化成 ++i 可以提高效率
for (int i = 0; i < 10; i++) { ... }

经过优化的语法树如图:

rd tree

因为直接在语法树上优化比较困难,所以源代码优化器往往将语法树转换成中间代码(Intermediate Code),它是语法树的顺序表示。

三地址码是一种常见的中间代码,它像这样:

x = y op z

这个三地址码表示将 y 和 z 进行 op 操作后,赋值给 x。这里 op 操作可以是算数运算,也可以是其他可以应用到 y 和 z 的操作。上述例子被翻译成三地址码后是这样的:

t1 = 2 + 6
t2 = index + 4
t3 = t2 * t1
arrat[index] = t3

这里用到了三个临时变量 t1、t2 和 t3。其中 t1 和 t3 都是不必要的,优化如下:

t2 = index + 4;
t2 = t2 * 8
arrat[index] = t2

目标代码生成及优化

该过程由代码生成器(Code Generator)和目标代码优化器(Target Code Optimizer)完成。

代码生成器将中间代码转化成目标机器代码,上述代码可能会生成如下代码序列(使用 x86 汇编语言表示,并假设 index 的类型为 int,array 的类型为 int 型数组):

movl index, %ecx			;	value of index to ecx
addl $4, %ecx				;	ecx = ecx + 4
mull $8, %ecx				;	ecx = ecx * 8
movl index, %eax			;	value of index to eax
movl %ecx, array(, eax, 4)	;	array[index] = ecx

最后目标代码优化器对上述目标代码进行优化,比如使用位移来代替乘法、删除多余指令等。乘法由一条相对复杂的基址比例变址寻址的 lea 指令完成,随后由一条 mov 指令完成最后的赋值操作。

movl index, %edx
leal 32(, %edx, 8), %eax
movl %eax, array(, %edx, 4)

经过上述所有过程,源代码最终变成了目标代码。但还有一个问题就是:index 和 array 的地址还没有确定。如果 index 和 array 定义在跟上面的源代码同一个编译单元里面,那么编译器可以为它们分配空间并确认它们的地址,那如果是定义在其他的程序模块呢?这就是之后链接要解决的问题了。

汇编

汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编过程相对简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译。

想得到汇编后的目标文件(Object File)可以使用如下命令(-c 表示汇编后便停止):

$ gcc -c hello.c -o hello.o

链接

假如我们在程序模块 main.c 中使用另外一个模块 func.c 中的函数 foo()。我们在 main.c 模块中每一处调用 foo 的地方都必须知道 foo 的地址,但是由于每个模块都是单独编译的,在编译器编译 main.c 的时候它并不知道 foo 函数的地址,所以它暂时把这些调用 foo 的指令的目标地址搁置,等待最后链接的时候由链接器去将这些指令的目标地址修正。

链接器在链接的时候,会根据我们所引用的符号 foo,自动在其他编译模块中查找,在 func.c 模块找到 foo 的地址,然后将它的地址填在 main.c 模块中引用到 foo 的指令的地方。

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

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

相关文章

百度飞浆在pycharm中的使用(含官网安装和cuda)

uieGitHub 安装cuda 1 获取版本 我的是 CUDA Toolkit 11.7.1 (August 2022), Versioned Online Documentation 为了防止后期版本不对应&#xff0c;我这里小心谨慎安装了August对应的月份。 C:\Users\89735>nvidia-smi Mon Dec 19 21:31:28 2022 ------------------------…

一眼万年,这3款顶级神软,内存满了也绝不卸载

免费软件都不好用&#xff1f;不&#xff01;下面3款良心软件&#xff0c;颠覆你的认知&#xff0c;功能强大到离谱&#xff0c;值得收藏往后有需要直接使用。 1、桌面运维助手 这是一款堪称神器的国产电脑优化工具&#xff0c;集硬件管理、系统管理、辅助工具于一体&#xff0…

Effective C++条款39:明智而审慎地使用private继承(Use private inheritance judiciously)

Effective C条款39&#xff1a;明智而审慎地使用private继承&#xff08;Use private inheritance judiciously&#xff09;条款39&#xff1a;明智而审慎地使用private继承1、private 继承2、在private继承和复合之间做出正确选择3、使用private继承比组合更加合理的例子4、牢…

wsl安装CUDA

NVCC 昨天已经安装好了gpu版的pytorch&#xff0c;对于一般的代码应该就可以运行了。但有些代码中需要用到cuda算子&#xff0c;需要配置nvcc环境。对于这个我也没能搞太清楚&#xff0c;网上的说法不一&#xff0c;我使用conda安装pytorch时也安装了cudatoolkit&#xff0c;按…

c++11 标准模板(STL)(std::forward_list)(八)

定义于头文件 <forward_list> template< class T, class Allocator std::allocator<T> > class forward_list;(1)(C11 起)namespace pmr { template <class T> using forward_list std::forward_list<T, std::pmr::polymorphic_…

当面试官问:“你还有什么要问我”,怎样回答才最加分?

面试到最后&#xff0c;面试官常常会问求职者&#xff1a;“你还有什么要问我&#xff1f;”许多人面对这个问题&#xff0c;不知该怎样回答&#xff0c;怕回答不好影响自己的面试结果&#xff0c;那么怎么回答才最加分呢&#xff1f;有人说&#xff0c;可以问问这个职位应该具…

springboot整合gateway网关

2.3 搭建Gateway 本项目使用Spring Cloud Gateway作为网关&#xff0c;下边创建网关工程。 新建一个网关工程。 工程结构 添加依赖&#xff1a; XML org.springframework.cloud spring-cloud-starter-gateway com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discove…

TryHackMe-Blog

Blog 比利乔尔&#xff08;Billy Joel&#xff09;在他的家用电脑上写了一个博客&#xff0c;并开始工作。这将是非常棒的&#xff01; 枚举此框并找到隐藏在其上的 2 个标志&#xff01;比利的笔记本电脑上有一些奇怪的事情。你能四处走动并得到你需要的东西吗&#xff1f;还…

蓝队常用的攻击手段

目录 一&#xff0c; 漏洞利用 1.1 SQL 注入漏洞 1.2 跨站漏洞 1.3 文件上传或下载漏洞 1.4 命令执行漏洞 1.5 敏感信息泄露漏洞 在实战过程中&#xff0c;蓝队专家根据实战攻防演练的任务特点逐渐总结出一套成熟的做法:外网纵向突破重点寻找薄弱点&#xff0c;围绕薄弱点…

2022 VeLO: Training Versatile Learned Optimizers by Scaling Up

VeLO: Training Versatile Learned Optimizers by Scaling Up 通过扩展模型的规模来训练一个通用的优化器。 设计上&#xff0c;优化器的原理基于元学习的思路&#xff0c;即从相关任务上学习经验&#xff0c;来帮助学习目标任务。 相比迁移学习&#xff0c;元学习更强调获取元…

2023年批量下载和改名音频专辑(单页列表)

一、下载原理 1&#xff09;找到目标音频的专辑网页&#xff0c;这里以 kite runner mp3为例。&#xff08;需要自己找&#xff09; https://www.xi___ma___la_____ya.com/album/71718770 2&#xff09;进入详细页&#xff08;称为一次请求URL&#xff09;&#xff08;不需要…

JUC(java.util.concurrent)的常见类

文章目录一、JUC常见类Callable 接口ReentrantLockSemaphore(信号量)CountDownLatch一、JUC常见类 concurrent代表了并发&#xff0c;这个包下为我们提供了并发编程(多线程)相关的组件. Callable 接口 我们的Callable接口和Runnable是一样的&#xff0c;但也有一些区别: Run…

C/C++实现跨年表白烟花

跨年表白烟花使用c/c实现烟花效果&#xff08;小白进&#xff09;分析诉求&#xff0c;拆分问题头文件贯穿全文的媒体部分文字部分&#xff1a;进入烟花弹部分烟花弹的属性初始化烟花弹让烟花弹飞起来烟花爆炸烟花弹的属性初始化烟花让烟花炸起来完成代码&#xff1a;使用c/c实…

840个最优的机器学习python开源项目整理分享

本资源包含了840个很棒的机器学习开源项目&#xff0c;总共270万颗星分为32个类别。所有项目均按项目质量得分排名&#xff0c;该得分是根据从GitHub和不同程序包管理器自动收集的各种指标计算得出的。资源整理自网络&#xff0c;资源获取见源地址&#xff1a;https://github.c…

三星手机提取微信聊天数据

三星手机提取微信聊天数据的方法&#xff0c;无需root。 注意&#xff0c;暴力破解密码需要英伟达显卡&#xff0c;一小时内破解&#xff0c;无显卡可能要两天。 1. 安装USB驱动&#xff0c;通过S换机助手&#xff0c;备份微信软件至电脑。注意&#xff0c;选择不加密。 三星…

[硬核] Bootstrap Blazor Table 综合演示例子

知识点: 1.导入导出 2.分页功能 3.增删改查 4.批量删除 5.批量编辑(审核) 6.列排序与列搜索 7.顶部搜索实现所有列搜索 8.高级搜索实现多条件搜索 9.顶部与刷新与视图列 10.实现文本类型明细行 11.列的统计 12.隐藏列,时间日期列格式化 13.新窗口打开 14.随机数据 15.自由编辑…

DVWA靶机CSRF全难度(未完)

目录 Low难度 medium难度 Cross Site Request Forgery跨站的请求伪造 原理&#xff1a;利用受害者尚未失效的身份认证信息、会话&#xff1b;诱骗其访问黑客设计号的页面&#xff0c;在受害人不知情的情况下以受害人的身份向服务器发送请求完成非法操作 Low难度 源代码 &l…

十二、RabbitMQ 报错汇总

&#x1f33b;&#x1f33b; 目录一、报版本过低问题一、报版本过低问题 问题&#xff1a; error: Failed dependencies: libcrypto.so.1.1()(64bit) is needed by erlang-25.1.2-1.el8.x86_64 libcrypto.so.1.1(OPENSSL_1_1_0)(64bit) is needed by erlang-25.1.2-1.el8.x86_…

基于模糊控制的自平衡小车的研究

1、内容简介略635-可以交流、咨询、答疑2、内容说明随着人类文明的发展&#xff0c;传感器技术、计算机应用技术、机械学、微电子技术、通讯技术以及人工智能技术也得到了飞速的发展。进入21世纪后&#xff0c;在机器人学和机器人技术领域&#xff0c;自平衡小车已成为其中的重…

LeetCode 2293. 极大极小游戏

【LetMeFly】2293.极大极小游戏 力扣题目链接&#xff1a;https://leetcode.cn/problems/min-max-game/ 给你一个下标从 0 开始的整数数组 nums &#xff0c;其长度是 2 的幂。 对 nums 执行下述算法&#xff1a; 设 n 等于 nums 的长度&#xff0c;如果 n 1 &#xff0c;…