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函数;也有可能是意外终⽌。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

冗余双写方案下数据一致性问题解决及延申问题处理方案

主要整理了采用冗余双写方案后的问题解决方案。 1、问题&#xff1a;冗余双写场景下&#xff0c;如何解决数据一致性问题&#xff1f; 方案一&#xff1a; 直接RPC调用Seata分布式事务框架&#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…

网络编程基本概念(一篇文章掌握基本内容的详细概念,IP,端口号,协议,协议分层,封装和分用,客户端和服务端,请求和回应,两台主机的通信)

IP地址 概念 IP地址主要⽤于标识⽹络主机、其他⽹络设备&#xff08;如路由器&#xff09;的⽹络地址。简单说&#xff0c;IP地址⽤于定位主机的⽹络地址。 就像我们发送快递⼀样&#xff0c;需要知道对⽅的收货地址&#xff0c;快递员才能将包裹送到⽬的地。 IP的格式 IP地址…

优化iproute2中的tc流控规则下发机制

设备基于IP对每个用户配置流量控制规则&#xff0c;规则如下&#xff1a; tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip src 192.168.0.10 flowid 1:10 上面的tc 是一个配置工具&#xff0c;本身是一个应用程序&#xff0c;tc后面的参数通过应用程序参…

vue3+threejs新手从零开发卡牌游戏(十八):己方场上手牌添加画线

手牌上场后&#xff0c;点击己方怪兽区卡牌会跟随鼠标移动画出线条&#xff0c;之后可以通过判断鼠标移动到对方场地的某卡牌进行战斗操作&#xff0c;代码主要改动在game/index.vue文件。 1.添加鼠标移动监听事件&#xff08;移动端&#xff09;&#xff1a; window.addEven…

Scikit Learn中的概率校准曲线

概率校准是一种用于将二分类的输出分数转换为概率的技术&#xff0c;以与目标类的实际概率相关联。在本文中&#xff0c;我们将讨论概率校准曲线以及如何使用Scikit-learn绘制它们。 概率校准 概率校准曲线是二分类问题的正类的预测概率和实际观察频率之间的图。它用于检查分…

DLS-42/5-5双位置继电器 柜内安装板前接线 JOSEF约瑟

系列型号&#xff1a; DLS-41/10-2双位置继电器&#xff1b; DLS-41/9-3双位置继电器&#xff1b; DLS-41/8-4双位置继电器&#xff1b; DLS-41/6-6双位置继电器&#xff1b; DLS-42/9-1双位置继电器&#xff1b; DLS-42/8-2双位置继电器&#xff1b; DLS-42/7-3双位置继…

理解CPU与执行指令原理

本文侧重介绍cpu的工作任务&#xff0c;与cpu执行指令的过程是怎么样的&#xff1f; 目录 1.理解CPU 1.1.CPU的功能 1.2.CPU的逻辑构成 2.认识指令 2.1.什么是指令 2.2.CPU执行指令的准备工作(重点) 3.指令的执行过程 前景知识&#xff1a; 什么是计算机 就是遵循冯诺依…

微信小程序使用Vant组件库流程

目前 Vant 官方提供了 Vue 2 版本、Vue 3 版本和微信小程序版本&#xff0c;并由社区团队维护 React 版本和支付宝小程序版本。这样开发原生微信小程序的会方便很多。 官方网址&#xff1a;Vant Weapp - 轻量、可靠的小程序 UI 组件库 步骤一 通过 npm 安装 npm i vant/weap…

RXMA1 RK211 075 AC220V中间继电器 柜内安装板前接线 JOSEF约瑟

系列型号 RXMA1 RK 211 063中间继电器&#xff1b;RXMA1 RK 211 064中间继电器; RXMA1 RK 211 066中间继电器&#xff1b;RXMA1 RK 211 072中间继电器; RXMA1 RK 211 073中间继电器&#xff1b;RXMA1 RK 211 074中间继电器&#xff1b; RXMA1 RK 211 025中间继电器&#xff1b;…

k8s入门到实战(十二)—— pod的深入理解

pod 深入理解 pod 容器生命周期 pod 的几种状态 可以使用命令kubectl get pod -w实时监控查看 pod 的状态 running&#xff1a;正常运行状态Pending&#xff1a;资源分配不对的时候会挂起&#xff0c;出现此状态Terminating&#xff1a;某个节点突然关机&#xff0c;上面的 p…

uniapp微信小程序消息订阅详解

一、微信公众平台申请订阅模板 注意&#xff1a;订阅信息 这个事件 是 当用户 点击的时候触发 或者 是 支付成功后触发&#xff0c; 用户勾选 “总是保持以上选择&#xff0c;不再询问” 之后或长期订阅&#xff0c;下次订阅调用 wx.requestSubscribeMessage 不会弹窗&#xf…

浅析扩散模型与图像生成【应用篇】(十三)——PITI

13. Pretraining is All You Need for Image-to-Image Translation 该文提出一种基于预训练扩散模型的图像转换方法&#xff0c;称为PITI。其思想并不复杂&#xff0c;就是借鉴现有视觉和NLP领域中常见的预训练方法&#xff0c;考虑预先在一个大规模的任务无关数据集上对扩散模…

基于java+springboot+vue实现的校园二手交易系统(文末源码+Lw+ppt)23-336

摘 要 自从新冠疫情爆发以来&#xff0c;各个线下实体越来越难做&#xff0c;线下购物的人也越来越少&#xff0c;随之带来的是一些不必要的浪费&#xff0c;尤其是即将毕业的大学生&#xff0c;各种用品不方便携带走导致被遗弃&#xff0c;造成大量的浪费。本系统目的就是让…

浏览器如何渲染页面

浏览器渲染页面的过程是一个多步骤的复杂流程&#xff0c;下面我将通过一个简单的例子来逐步说明这一过程&#xff1a; 以下是浏览器渲染该页面的主要步骤&#xff1a; 请求HTML文档&#xff1a; 用户在浏览器中输入URL并回车&#xff0c;浏览器向服务器发送HTTP请求。 服务器…

【Java多线程】1——多线程知识回顾

1 多线程知识回顾 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记仓库&#x1f449;https://github.com/A-BigTree/tree-learning-notes 个人主页&#x1f449;https://www.abigtree.top ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star…

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

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

数论问题代码模板

文章目录 一、质数1.1、质数筛&#xff08;筛1~n中的所有质数&#xff09;1.2、判断一个数是否为质数1.3、对一个数进行质因数分解 二、快速幂2.1、费马小定理——乘法逆元2.2、快速幂 三、约数3.1、N个数的正约数集合3.2、一个数的正约数集合 四、欧拉函数&#xff08;互质数数…

洗地机好用吗?哪款型号值得推荐?看完本文你就知道

在如今社会生活节奏不断加快的情况下&#xff0c;洗地机已经成为众多家庭的必备的清洁设备&#xff0c;面对市面上种类繁多的洗地机&#xff0c;我们常常会发出感叹“洗地机好用吗&#xff1f;洗地机哪个型好用&#xff1f;”等的疑问&#xff0c;今天&#xff0c;为了帮助大家…

一文搞定用python实现终身免费的听书工具

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…