编译与链接

gcc/g++ 链接库的编译与链接 gcc/g++ 链接库的编译与链接 surgewong@gmail.com http://blog.youkuaiyun.com/surgewong       程序编译一般需要经预处理、编译、汇编和链接几个步骤。在实际应用中,有些公共代码需要反复使用,就把这些代码编译成为“库”文件。在链接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中,这种库称为静态(链接)库,其特点是可 阅读详情

编译与链接

代码运行背后的过程

经典C语言程序:


#include<stdio.h>

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

Linux下,使用gcc来编译这个程序,进入命令行输入:


gcc hello.c

可以看到文件下面多出了一个a.out文件:

Linux的.out是由gcc编译生成的二进制格式文件,但有可能是执行程序也可能是链接库文件,因为在linux中扩展名除了一些特殊的约定,一般情况下是无意义的。在使用gcc编程时,没有指定输入可执行文件名,默认生成可执行文件a.out文件

图1.png

然后接着执行指令:


./a.out

输出:

图2.png

当然你也可以指定自己想要的输出文件的文件名字

使用如下指令:


gcc hello.c -o hello.out

结果:

图3.png

上面的过程可以分解为4个步骤:

  • 预处理

  • 编译

  • 汇编

  • 链接

GCC编译过程分解图示

图4.png

预编译

源代码文件hello.c与头文件,如stdio.h等被预编译器cpp预编译成一个.i文件

对应指令:


gcc -E hello.c -o hello.i

执行后:

图5.png

处理规则:

  • 处理关于 “#” 的指令

  • 删除#define,展开所有宏定义。例#define portnumber 3333

  • 处理条件预编译 #if, #ifdef, #if, #elif,#endif

  • 处理“#include”预编译指令,将包含的“.h”文件插入对应位置。这可是递归进行的,文件内可能包含其他“.h”文件。

  • 删除所有注释。/**/,//。

  • 添加行号和文件标识符。用于显示调试信息:错误或警告的位置。

  • 保留#pragma编译器指令。(1)设定编译器状态,(2)指示编译器完成一些特定的动作

经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开了,并且包含的文件也已经被插入到.i文件

编译

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

执行编译过程的指令:


gcc -S hello.i -o hello.s

执行后:

图6.png

得到汇编文件hello.s

汇编

汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应着一条机器指令

执行汇编过程的命令:


gcc -c hello.s -o hello.o

执行后:

图7.png

得到目标文件hello.o

链接

为何要链接?汇编器为何不直接输出可执行文件而是输出一个目标文件?此处留下疑问

编译器做了哪些工作

编译过程是整个程序构建的核心部分,编译成功,会将源代码由文本形式转换成机器语言,编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件。

编译过程可以分6步:

  • 扫描

  • 语法分析

  • 语义分析

  • 源代码优化

  • 代码生成

  • 目标代码优化

图示:

图8.png

词法分析

词法分析是使用一种叫做lex的程序实现词法扫描,它会按照用户之前描述好的词法规则将输入的字符串分割成一个个记号。产生的记号一般分为:关键字、标识符、字面量(包含数字、字符串等)和特殊符号(运算符、等号等),然后他们放到对应的表中。

语法分析

语法分析器根据用户给定的语法规则,将词法分析产生的记号序列进行解析,然后将它们构成一棵语法树。对于不同的语言,只是其语法规则不一样。用于语法分析也有一个现成的工具,叫做:yacc。

语义分析

语法分析完成了对表达式语法层面的分析,但是它不了解这个语句是否真正有意义。有的语句在语法上是合法的,但是却是没有实际的意义,比如说两个指针的做乘法运算,这个时候就需要进行语义分析,但是编译器能分析的语义也只有静态语义。

静态语义:在编译期就可以确定的语义。通常包括声明与类型的匹配、类型的转换。比如当一个浮点型的表达式赋值给一个整型的表达式时,其中隐含一个从浮点型到整型的转换,而语义分析就需要完成这个转换,再比如,将一个浮点型的表达式赋值给一个指针,这肯定是不行的,语义分析的时候就会发现两者类型不匹配,编译器就会报错。

动态语义:只有在运行期才能确定的语义。比如说两个整数做除法,语法上没问题,类型也匹配,听着好像没毛病,但是,如果除数是0的话,这就有问题了,而这个问题事先是不知道的,只有在运行的时候才能发现他是有问题的,这就是动态语义。

中间代码生成

我们的代码是可以进行优化的,对于一些在编译期间就能确定的值,是会将它进行优化的,比如说上边例子中的 2+6,在编译期间就可以确定他的值为8了,但是直接在语法上进行优化的话比较困难,这时优化器会先将语法树转成中间代码。中间代码一般与目标机器和运行环境无关。(不包含数据的尺寸、变量地址和寄存器的名字等)。中间代码在不同的编译器中有着不同的形式,比较常见的有三地址码和P-代码。

中间代码使得编译器可以分为前端和后端。编译器前端负责产生于机器无关的中间代码,编译器后端将中间代码换成机器代码。

目标代码生成与优化

代码生成器将中间代码转成机器代码,这个过程是依赖于目标机器的,因为不同的机器有着不同的字长、寄存器、数据类型等。

最后目标代码优化器对目标代码进行优化,比如选择合适的寻址方式、使用唯一来代替乘除法、删除出多余的指令等。

到现在,经历了这么多步,源代码终于翻译成了目标代码,但此时出现了一个问题:

假设目标代码中我们定义了变量,如:


int index;

int[] array;

那么index与array的地址从哪里得到?

如果目标代码中有变量定义在其他模块,那怎么确定?

最终,定义在其他模块的全局变量以及函数在最终运行时的绝对地址都要在最终链接的时候才能确定,所以现代的编译器可以将一个源代码文件编译成一个未链接的目标文件,然后由链接器最终将这些目标文件链接起来形成可执行文件。

初识链接

计算机发展初期,写程序都是使用机器语言,一条指令一条指令的书写。

比如:


0 0001 0100

1 ........

2 ........

3 ........

4 1000 0111

5 .......

上面是一段程序,但是程序写好之后可能很大概率还是要修改的,如在上面第一条指令之后,第五条指令之前插入n条指令,那么第五条指令以及后面的指令的位置需要相应的后移,那么可能就需要重新计算子程序或者其他涉及的目标地址。

这种重新计算各个目标的地址过程就叫重定位

但是这种方法太低效了,所以就出现了汇编语言,使用各种接近人类的符号和标记来帮助记忆。使用符号的方法可以使得人们从具体的指令地址中解放出来

假设上面的二进制指令中,第1条指令的作用是跳转到第5条指令,那么使用汇编可以把第五条指令开始的子程序命名为“foo”,那么第一条指令对应的汇编就是:

jmp foo

使用这种符号的方式后,不管这个“foo”之前插入或者减少了多少条指令导致“foo”目标地址发生了任何变化,汇编器每次汇编程序的时候会重新计算“foo”这个符号的地址,然后把所有引用到“foo”的指令修正到正确地址。无需人工参与。

随着软件开发规模的庞大,我们开始将代码按照功能或性质进行划分,形成不同的功能模块,不同模块之间按照层次结构或者其他来组织。比如C语言中,最小的单位是变量和函数,若干个变量和函数组成一个模块,存放在一个“.c”的源代码文件里面,然后这些源代码文件按照目录结构来组织

一个程序被分割成多个模块之后,模块之间最后如何组合形成一个单一的程序是一个问题。模块之间如何组合的问题归结为模块之间的如何通信的问题,最常见的属于静态语言的C/C++模块之间通信有两种方式:

  • 模块间的函数调用

  • 模块间的变量访问

函数访问需要知道目标函数的地址,变量访问也需要知道目标变量的地址,所以这两种方式都可以归结为一种方式:模块间符号的引用

模块间依靠符号来通信类似于拼图版,定义符号的模块多出了一块区域,引用该符号的模块刚好少了那一块区域,两者以拼接刚好完美组合。这个模块的拼接过程就是—链接

模块拼装—静态链接

对于复杂软件,人们把每个源代码模块独立的编译,然后按照需要将它们“组装”起来,这个组装模块的过程就是链接

链接的过程主要包括:

  • 地址和空间分配

  • 符号决议

  • 重定位

图示:

图9.png

每个模块的源代码文件(如.c)文件经过编译器编译成目标文件(后缀为.o或者.obj),目标文件和库(Library)一起链接形成最终可执行文件

链接过程

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

假设没有链接器,那么我们就需要手工的把每个调用foo的指令进行修正,再填入正确的foo函数地址,当func.c函数被重新编译时,foo函数的地址可能会发生改变,那么在main.c中所有使用到foo的地址的指令将要全部重新调整,这无疑将成为噩梦。

有了链接器,可以直接引用其他模块的函数和全局变量而无需知道它们的地址,因为链接器在链接的时候会根据你所引用的符号foo,自动的去相应的func.c模块查找foo的地址,然后将main.c模型所有引用到foo的指令重新修正,让它们的目标地址为真正的foo函数的地址,这就是静态链接最基本的过程和作用

参考资料

<<程序员的自我修养—链接、装载与库>>

https://blog.youkuaiyun.com/guaiguaihenguai/article/details/81160310

【iOS】—— 编译链接 dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统内核 XNU 完成 Mach-O 文件的加载,做好程序准备工作之后,交由 dyld 负责余下的工作。​ **优点:**节省磁盘空间,且多个用到相同动态库的程序同时运行,库文件会进行共享。**在编译的时候动态库不会被拷贝到程序中,只会存储下动态库的引用,等找到真正使用的时候才回去查找-绑定-使用函数。​ **优点:**在编译链接的时候被整合到可执行文件中,是程序运行的时候独立,不依赖外部库。 阅读详情

相关推荐

H3C交换机S-MLAG实战:如何用bond4模式实现服务器高可用(附避坑指南)

本文详细介绍了H3C交换机S-MLAG技术与服务器bond4模式的实战配置,帮助实现跨设备链路聚合的服务器高可用方案。通过解析S-MLAG技术优势、bond4模式选择、关键配置步骤及常见问题排查,提供了一套完整的避坑指南和优化建议,适用于企业级网络架构的高可用需求。

weixin_30790841的博客 465

C语言源代码转变为可执行程序的过程

Hello.c #include<stdio.h> int main() { printf("Hello World\0"); return 0; } 我们在VS下只需要Ctrl+F5便能打印出Hello World, 在Linux下也只需要两行命令 gcc Hello.c ./a.out 在源代码文件转变为可执行文件中间发生了什么 ...

这是书生呀 1万+

用VASPKIT+Quantum Espresso高效绘制能带图:从K点路径生成到可视化全流程

本文详细介绍了如何整合VASPKIT与Quantum Espresso(QE)软件,构建一套高效的能带计算与可视化工作流。通过VASPKIT自动化生成高对称K点路径,结合QE完成自洽与非自洽计算,并利用gnuplot绘制发表级能带图。该流程极大提升了材料电子结构研究的效率与可重复性,是进行DFT能带计算的实用指南。

weixin_29294975的博客 163

【无标题】

gcc hello.c -o hello, 编译hello.c文件生成名为hello的可执行文件,直接./hello,可以运行可执行文件。gcc 文件名.c,默认生成一个名为a.out的可执行文件,执行可执行文件./a.out。在Linux中,C语言源程序的拓展名.c,所以C程序的源文件名字应该是xxx.c。二进制转十六进制,每四位二进制数转一位十六进制数,四位二进制数依次是8421。二进制、十进制、八进制、十六进制、六十进制。预处理----编译----汇编---链接。

Y_mqm的博客 447

YOLO环境配置+项目启动全流程(Conda+PyCharm超详解)

YOLO(You Only Look Once)是一个基于深度学习的目标检测算法,广泛应用于智能识别领域。本篇保姆级教学将手把手教你如何从零开始运行一个YOLO项目,适合完全没有基础的新手。

一键难忘的博客 1536

WiFi偏门协议(四):增强的传输机制802.11aa

区分Unicast和Multicast的优先级,以及设定不同的重传次数,以细化EDCA机制。 SCS(StreamClassification Service) 如上图,在VI,VO内各自又增加了一个queue, 对应不同的MAC header。VI和VO内的两个queue可以分别对应UC和MC。这样就提供了同一个AC内的更细粒度的优先级区分。当然还可以使用Drop Eligibility Indication子域来区分不同的重传次数。 改善Multicast数据传...

reekyli的专栏 1790

C语言 二进制文件-代码记录

【代码】C语言 二进制文件-代码记录。

m0_59179752的博客 685

OpenHarmony WebView引擎:从Chromium魔改到分布式渲染的终极解析

ArkWeb框架提供了强大的Web应用开发能力,但同时也需要注意网络安全问题,尤其是跨域请求方面。

m0_67505153的博客 1194

什么是编译和链接(通俗易懂,深入本质)

我们平时所说的程序,是指双击后就可以直接运行的程序,这样的程序被称为可执行程序(Executable program)。

qq_64844180的博客 1万+

YOLOv13涨点改进 | 全网独家创新、HyperACE改进、注意力改进篇 | CVPR 2025| 引入LRSA局部区域自注意力模块,能提升其在细节捕捉、小目标检测和长距离依赖建模方面的能力

本文提出将LRSA(局部区域自注意力)模块集成到YOLOv13中,通过局部特征交互增强细节捕捉能力、小物体检测精度和长距离依赖建模。LRSA模块采用重叠图像块处理策略,在保持计算效率的同时提升复杂背景下的检测性能。文章详细介绍了模块结构、原理优势,并提供了三种改进方案:1)在主干网络添加LRSA层;2)将关键模块替换为DSC3k2_LRSA;3)采用HyperACE_LRSA结构。配套提供了完整的PyTorch实现代码和YAML配置文件,展示了该模块如何通过局部注意力机制平衡检测精度与推理速度。

Ai缝合怪、助力小伙伴高效发论文 344

使用Transformer实现自动调制识别(RML2016a)

image_size = 128 # resize重置输入大小patch_size = 2 # 从输入图片(信号看作图片)中将被提取的块大小# 分成 num_patches 个块num_patches = (image_size // patch_size) #(72/6)**2 横着裁剪),],return x)(x1, x1)# MLP.# Add MLP.x=x_train,y=y_train,filepath,round。

QAQIknow的博客 6274

编译和链接(超详细)

🔹分享c语言知识及代码。

wcblh的博客 2068

2026年全国青少年信息素养大赛复赛真题(算法应用主题赛C++初中组:带解析)(7月4日试卷一)【文末附答案和解析】

2026年全国青少年信息素养大赛复赛真题(算法应用主题赛C++初中组:带解析)(7月4日试卷一)【文末附答案和解析】

王老师青少年编程 1732

编译链接过程

编译链接过程 C/C++程序从文本到可执行文件之间是一个复杂的过程. 对于源代码(.c/.cpp)文件我们是不能直接运行的, 必须经过一系列的处理才能转化为机器语言, 再通过链接相应的文件转化为可执行程序. 这个过程称为编译链接过程. 下面是从源代码到可执行文件的整个编译链接的过程: 整个编译链接过程无非就分为 编译过程 和 链接过程 1. 编译过程 C文件编译过程又...

YancyKahn 5877

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

编译链接

1,编译链接 编译链接分为4个步骤:预处理、编译、汇编、链接 2,预处理(预编译) 预编译过程把源文件变成.i文件,主要处理以#开头的预编译指令,比如#include、#define等 主要处理规则: 2,编译 编译是把预处理完的文件进行一系列词法分析、语法分析、语义分析、优化后,生成相应的汇编代码文件。 3,汇编 汇编器将汇编代码变成机器指令,没有复杂语法,也没有语义,也不需要优化,只需要一一对应的翻译即可。 4,链接 链接过程生成可执行文件。 ...

nameofcsdn的博客 8567

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

编译 与 链接 详解

编译与链接过程详解 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程 从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。 过程...

lovehang99的博客 3419

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

编译和链接

编译和链接

遇见的博客 1531

编译与静态链接

最近一段时间做了关于一些软件的交叉编译工作,由于觉得并没有学到许多东西,所以抽时间看了关于静态链接方面的内容,读了一部分《程序员的自我修养——链接、装载与库》,记录一些读书笔记及自己的总结。 一、GCC做了什么? 我们在Linux中经常使用 gcc 源文件名.c -o 目标可执行文件名 对一个编写好的C文件进行编译生成可执行文件,那么这之间的细节呢?  这里源文

taocr的博客 4024

程序的编译与链接

程序编译链接过程中的细节

yue152152的博客 2619

C++编译链接

C++分离式编译,编译链接过程

西电卢本伟的博客 1727

编译与链接总结

编译与链接总结1.预处理2.编译a.词法分析b.语法分析c.语义分析d.中间语言的生成e.目标代码的优化和生成3.链接a.静态链接b.动态链接 编译与链接的过程可以分解为4 个步骤,分别是预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。 编译与链接过程: 1.预处理 将源代码文件即后缀为.cpp和相关的头文件通过预处理器预处理为...

NEUChords的博客 475

编译链接原理

编译链接原理的两种角度描述,详细描述链接部分。

weixin_50890461的博客 1054

C++的编译链接

了解C++的编译链接过程

冯同学的博客 1677

编译、链接过程简介

目录 1、初探GDB 2、使用GDB 进行断点调试 3、函数调用栈的查看 4、调试中的小技巧 5、小结 1、初探GDB 什么是GDB? - GNU项目中的调试器(gnu debuger) - 能够跟踪程序的执行,也能够恢复程序崩溃前的状态 为什么需要GDB? - 软件不是一次性开发完成的(是软件就有bug,是程序就有问题) -...

洋葱汪的博客 1764
上一篇: 实现一个简单的词法分析器
下一篇: JVM与synchronized
Coder_py
博客等级 码龄10年 106粉丝 258原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值