C语言看完我这篇编译与链接就够啦!!!

1. 前言

Hello!大家好我是小陈,今天来给大家介绍最详细的C语言编译与链接。

2. 编译和链接

我们通常用的编译器,比如Visual Sudio,这样的IDE(集成开发环境)一般将编译和链接的过程一步完成,通常将这这种编译和链接合并到一起的过程称为构建bulid。
我们经常使用这些编译器后,就不会用一些指令再去编译和链接,因为强大的集成开发环境已经满足了这些编译和链接,但是在代码出错的时候,有时我们会无从下手,所以我觉得我们还是需要深入了解编译和链接的具体步骤。

2.1 被隐藏了的过程

只要你稍微学习一点编程,那么下面的语句你并不陌生,因为这是每个程序员刚开始学的第一个语句,我们在vs编译器操作如下。

#include <stdio.h>
int main()
{char arr[] = { "hello world!!!" };printf("%s\n",arr);//你好世界return 0;
}

image.png
在 Linux 下,当我们使用 GCC 来编译 Hello World 程序时,只须使用最简单的命令(假设源代码文件名 hello.c):

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

事实上,上面可以分解为4个步骤,分别是预处理(Prepressing)编译(Compilation)汇编(Assembly)和链接(Linking)。
image.png

2.1.1 预编译

首先是源代码文件 hello.c 和相关的头文件,如 stdio.h 等被 预编译器 cpp 预编译成一个 .i 文件。对于 C++ 程序来说,它的源代码文件的扩展名可能是 .cpp 或 .cxx,头文件的扩展名可能是 .hpp,而预编译后的文件扩展名是 .ii。第一步预编译的过程相当于如下命令(-E 表示只进行预编译):

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

or

$ cpp hello.c > hello.i

预编译过程主要处理那些源代的文件中的以 # 开始的预编译指令。比如 #include、#define 等,主要处理规则如下:

  • 将所有的 #define 删除,并且展开所有的宏定义。
  • 处理所有条件编译指令,比如:#if、#ifdef、#elif、#else、#endif。
  • 处理 #include 预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
  • 删除所有的注释 // 和 /* */。
  • 添加行号和文件名标识,比如 #2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。
  • 保留所有的 #pragma 编译器指令,因为编译器须要使用它们。

当然我们要注意,经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到.i文件中。所以当我们无法判断宏定义是否正确,或者头文件包含是否正确时,可以查看预编译后的文件来确定问题。

2.1.2 编译

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

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

如何进行编译呢?

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

2.1.3 汇编

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

$ as hello.s -o hello.o

or

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

使用 gcc 命令从 C 源代码文件开始,经过预编译、编译和汇编直接输出 目标文件

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

2.1.4 链接

链接说白了就是把许多文件链接在一起,但是这些文件格式好多不相同,有着一步下一步的关系
链接的过程主要包括 :地址和空间分配,符号决议和重定位等这些步骤。
链接解决的是一个项目中的文件,多模块之间互相调用的问题。

ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lgcc_eh -lc
-end-group crtend.o crtn.o
//test.c
#include <stdio.h>
//test.c
//声明外部函数
extern int Add(int x, int y);
//声明外部的全局变量
extern int g_val;
int main()
{int a = 10;int b = 20;int sum = Add(a, b);printf("%d\n", sum);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.2 编译器干了什么活

从最直观的角度来讲,编译器就是将高级语言翻译成机器语言的一个工具。比如我们用 C/C++ 语言写的一个程序可以使用编译器将其翻译成机器可以执行的指令及数据。我们前面也提到了,使用机器指令或汇编语言编写程序是十分令费事及乏昧的事情,它们使得程序开发的效率十分低下。并且使用机器语言或汇编语言编写的程序依赖于特定的机器,一个为某种 CPU 编写的程序在另外一种 CPU 下完全无法运行,需要重新编写,这几乎是令人无法接受的。
image.png
用一行C语言代码的例子来讲述

array[index] = (index + 4) * (2 + 6)
CompilerExpression.c

2.2.1 词法分析

首先源代码程序被输入到 扫描器(Scanner),扫描器的任务很简单,它只是简单地进行词法分析,运用一种类似于 有限状态机(Finite State Machine) 的算法可以很轻松地将源代码的字符序列分割成一系列的 记号(Token)。
image.png
词法分析产生的记号一般可以分为如下几类:关键字、标识符、字面量(包含数字、字符串等) 和 特殊符号(如加号、等号)。在识别记号的同时,扫描器也完成了其他工作。比如将标识符存放到 符号表,将数字、字符串常量存放到 文字表 等,以备后面的步骤使用。
有一个叫做 lex 的程序可以实现词法扫描,它会按照用户之前描述好的 词法规则 将输入的字符串分割成一个个记号。因为这样一个程序的存在,编译器的开发者就无须为每个编译器开发一个独立的词法扫器,而是根据需要改变词法规则就可以了。
另外对于一些有预处理的语言,比如 C 语言,它的宏替换和文件包含等工作一般不归入编译器的范围而交给一个独立的预处理器。

2.2.2 语法分析

语法分析器(Grammar Parser) 将对由扫描器产生的记号进行语法分析,从而产生 语法树(Syntax Tree)。
上下文无关语法(Context-free Grammar)在计算机科学中,若一个 形式文法 G = (N, Σ, P, S) 的产生式规则都取如下的形式:V->w,则谓之。 其中 V∈N ,w∈ (N∪Σ)*
C语言上下文无关法的定义
语法树
image.png

2.2.3 语义分析

编译器所能分析的语义是 静态语义(Static Semantic),所谓静态语义是指在编译期可以确定的语义,与之对应的 动态语义(Dynamic Semantic) 就是只有在运行期才能确定的语义。
静态语义通常包括声明和类型的匹配,类型的转换。比如当一个浮点型的表达式赋值给一个整型的表达式时,其中隐含了一个浮点型到整型转换的过程,语义分析过程中需要完成这个步骤。

2.2.4 中间语言生成

三地址码(Three-address Code) 和 P-代码(P-Code)。我们就拿最常见的三地址码来作为例子

x = y op z

这个三地址码表示将变量 y 和 z 进行 op 操作以后,赋值给 x。这里 op 操作可以是算数运算,比如加减乘除等,也可以是其他任何可以应用到 y 和 z 的操作。三地址码也得名于此,因为一个三地址码语句里而有三个变量地址。我们上面的例子中的语法树可以被翻译成三地址码后是这样的:

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

我们可以看到,为了使所有的操作都符合三地址码形式,这里利用了几个临时变量: t1、t2 和 t3。在三地址码的基础上进行优化时,优化程序会将 2+6 的结果计算出来,得到 t1 = 6。然后将后面代码中的 t1 替换成数字 6。还可以省去一个临时变量 t3,因为 t2 可以重复利用。经过优化以后的代码如下:

t2 = index + 4
t2 = t2 * 8
array[index] := t2

中间代码使得编译器可以被分为前端和后端。编译器前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。这样对于一些可以跨平台的编译器而言,它们可以针对不同的平台使用同一个前端和针对不同机器平台的数个后端。

2.2.5 目标代码生成与优化

源代码级优化器产生中间代码标志着下面的过程都属于编译器后端。编译器后端主要包括 代码生成器(Code Generator) 和 目标代码优化器(Target Code Optimizer)。

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
//上面都是一个代码反汇编后的指令

面的例子中,乘法由一条相对复杂的 基址比例变址寻址(Base Index Scale Addressing) 的 lea 指令完成,随后由一条 mov 指令完成最后的赋值操作,这条 mov 指令的寻址方式与 lea 是一样的:

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

2.3 链接器年龄比编译器长

参考书籍:《C语言程序员的自我修养》

2.4 模块拼装-静态链接

程序设计的模块化是人们一直在追求的目标,因为当一个系统十分复杂的时候,我们不得不将一个复杂的系统逐步分割成小的系统以达到各个突破的目的。一个复杂的软件也如此,人们把每个源代码模块独立地编译,然后按照须要将它们“组装”起来,这个组装模块的过程就是 链接(Linking)。链接的主要内容就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确地衔接。链接器所要做的工作其实跟前面所描述的“程序员人工调整地址”本质上没什么两样,只不过现代的高级语言的诸多特性和功能,使得编译器、链接器更为复杂,功能更为强大,但从原理上来讲,它的工作无非就是把一些指令对其他符号地址的引用加以修正。链接过程主要包括了 地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution) 和 重定位(Relocation) 等这些步骤。

符号决议有时候称为 符号绑定(Symbol Binding)、名称绑定(Name Binding)、名称决议(Name Resolution),甚至还有叫做 地址绑定(Address Binding)、指令绑定(Instruction Binding) 的,大体上它们的意思都一样,但从细节角度来区分,它们之间还是存在一定区别的,比如“决议”更倾向于静态链接,而“绑定”更倾向于动态链接,即它们所使用的范围不一样。在静态链接,我们将统一称为 符号决议。

2.5 运行环境

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

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

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

相关文章

算法(6)KMP+trie

KMP&#xff1a; 最浅显易懂的 KMP 算法讲解_哔哩哔哩_bilibili 该视频使用python书写代码&#xff0c;不会python的小伙伴也可以看看了解kmp的大致思路。 问题描述&#xff1a; kmp&#xff1a;字符串匹配算法&#xff0c;用来找一个长字符串中出现了几次小字符串&#xf…

【机器学习300问】54、如何找到有效的组合特征?

一、为什么需要去寻找有效的组合特征&#xff1f; 因为并不是所有的特征组合都会意义&#xff0c;都能带来价值。 例如在房价预测场景中&#xff0c;卧室数量和浴室数量的比值有意义&#xff0c;但房屋面积与建造年份相组合作为新的组合特征&#xff0c;可能就没有实际含义&…

【ORB-SLAM3】在 Ubuntu20.04 上编译 ORM-SLAM3 并使用 D435i、EuRoC 和 TUM-VI 运行测试

【ORB-SLAM3】在 Ubuntu20.04 上编译 ORM-SLAM3 并使用 D435i、EuRoC 和 TUM-VI 运行测试 1 Prerequisites1.1 C11 or C0x Compiler1.2 Pangolin1.3 OpenCV1.4 Eigen3 2 安装 Intel RealSense™ SDK 2.02.1 测试设备2.2 编译源码安装 (Recommend)2.3 预编译包安装 3 编译 ORB-S…

PTA L2-037 包装机

一种自动包装机的结构如图 1 所示。首先机器中有 N 条轨道&#xff0c;放置了一些物品。轨道下面有一个筐。当某条轨道的按钮被按下时&#xff0c;活塞向左推动&#xff0c;将轨道尽头的一件物品推落筐中。当 0 号按钮被按下时&#xff0c;机械手将抓取筐顶部的一件物品&#x…

07、Lua 流程控制

Lua 流程控制 Lua 流程控制控制结构语句 Lua 流程控制 Lua编程语言流程控制语句通过程序设定一个或多个条件语句来设定。在条件为 true 时执行指定程序代码&#xff0c;在条件为 false 时执行其他指定代码。 以下是典型的流程控制流程图&#xff1a; 控制结构的条件表达式结…

vscode c++环境配置

1.基础软件安装 安装Visual Studio Code. 安装C拓展。点击在vscode界面最左侧的Extensions图标&#xff08;打开快捷键&#xff1a;ctrlshiftX&#xff09;&#xff0c;搜索“C/C”&#xff0c;点击进行安装。 确保已安装gcc. 一般ubuntu系统会预装gcc.在终端窗口中输入如下…

查立得源码如何去除版权

最近发现很多人百度&#xff1a;查立得源码如何去除版权。 每个源代码/软件都是有版权的&#xff0c;无法去除&#xff0c;我们也得尊重知识产权/劳动成果。 可以去除/修改的是&#xff1a;页面显示的版权信息,查立得底部信息均可自定义(一般conn.php可修改)。 另&#xff1…

Linux-2 Linux的权限

目录 1.什么是权限&#xff1f; 2.权限的本质 3.Linux中的用户 普通用户与root用户相互转换 普通用户不变root&#xff0c;以root身份执行一个命令 LInux中的角色 4.Linux文件的权限 5.快速掌握修改权限的做法 修改权限 6.对比权限有无表现 对于普通用户&#xf…

五分钟,零基础也能入门 Python 图像文字识别

一. 前言 最近在研究 Python 的一些功能 &#xff0c; 也尝试了一些有趣实现&#xff0c; 这一篇就从实践的角度来研究一下 Python 如何实现图片识别。 众所周知 &#xff0c; Python 的库真的老多了&#xff0c;其中在图像识别上比较突出的就是 OpenCV. 那么基于这个库我们…

基于RAG的大模型知识库搭建

什么是RAG RAG(Retrieval Augmented Generation)&#xff0c;即检索增强生成技术。 RAG优势 部分解决了幻觉问题。由于我们可以控制检索内容的可靠性&#xff0c;也算是部分解决了幻觉问题。可以更实时。同理&#xff0c;可以控制输入给大模型上下文内容的时效性&#xff0c…

Day50:WEB攻防-PHP应用文件包含LFIRFI伪协议编码算法无文件利用黑白盒

目录 文件包含-原理&分类&利用&修复 文件读取 文件写入 代码执行 远程利用思路 黑盒利用-VULWEB 白盒利用-CTFSHOW-伪协议玩法 78-php&http协议 79-data&http协议 80-81-日志包含 87-php://filter/write&加密编码 88-data&base64协议 …

17、GateWay和Sentinel继承实现服务限流

注&#xff1a;本篇文章主要参考周阳老师讲解的cloud进行整理的&#xff01; 1、需求说明 cloudalibaba-sentinel-gateway9528 保护 cloudalibaba-provider-payment9001 2、启动nacos服务器8848 startup.cmd -m standalone 3、启动sentinel服务器8080 java -jar sentinel-dash…

企业微信变更主体公证怎么弄?

企业微信变更主体有什么作用&#xff1f;现在很多公司都用企业微信来加客户&#xff0c;有时候辛辛苦苦积累了很多客户&#xff0c;但是公司却因为各种各样的原因需要注销&#xff0c;那么就需要通过企业微信变更主体的方法&#xff0c;把企业微信绑定的公司更改为最新的。企业…

恶意勒索软件VirLock,通过ApkAnalyser一键提取安卓应用APK敏感信息

恶意勒索软件VirLock&#xff0c;通过ApkAnalyser一键提取安卓应用APK敏感信息。 ############################# 免责声明&#xff1a;本文仅作收藏学习之用&#xff0c;亦希望大家以遵守《网络安全法》相关法律为前提学习&#xff0c;切勿用于非法犯罪活动&#xff0c;对于恶…

python和c语言的区别是什么

Python可以说是目前最火的语言之一了&#xff0c;人工智能的兴起让Python一夜之间变得家喻户晓&#xff0c;Python号称目前最最简单易学的语言&#xff0c;现在有不少高校开始将Python作为大一新生的入门语言。本萌新也刚开始接触Python&#xff0c;发现Python与其他语言确实有…

linux查找指定目录下包含指定字符串文件,包含子目录

linux查找指定目录下包含指定字符串的文件&#xff0c;包含子目录 linux查找指定目录下包含指定字符串的指定文件格式&#xff0c;包含子目录 指定目录 cd /home/www/linux查找指定目录下包含指定字符串的文件&#xff0c;包含子目录 grep -r "指定字符串"注释 gr…

测试开发工程师(QA)职业到底需要干些什么?part6:数据测试工程师QA

常见任务和工作内容 作为数据测试工程师QA&#xff08;Quality Assurance&#xff09;&#xff0c;您的主要职责是确保数据的质量、准确性和一致性。以下是数据测试工程师QA的一些常见任务和工作内容&#xff1a; 数据验证和准确性测试&#xff1a;您将负责验证数据的准确性和…

Python基础教程:基本数据类型

基本数据类型 不可变数据(3 个):Number(数字)、String(字符串)、Tuple(元组) 可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合) Numbers(数字) 数字数据类型用于存储数值。 他们是不可改变的数据类型,这意味着改变数字数据类型会分配一个新的对…

【爬虫基础】第1讲 网络爬虫基本知识

什么是网络爬虫 网络爬虫&#xff08;Web crawler&#xff09;是一种自动化程序&#xff0c;用于在互联网上收集信息。它可以通过扫描和解析网页的超链接&#xff0c;自动访问网页并抓取所需的数据。网络爬虫常用于搜索引擎和数据采集工具中。 作用 通过有效的爬虫手段批量采…

鸿蒙HarmonyOS应用开发——组件级配置

在开发应用时&#xff0c;需要配置应用的一些标签&#xff0c;例如应用的包名、图标等标识特征的属性。本文描述了在开发应用需要配置的一些关键标签。 应用包名配置 应用需要在工程的AppScope目录下的 app.json5配置文件 中配置bundleName标签&#xff0c;该标签用于标识应用…