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

文章目录

      • 一、翻译环境和运行环境
        • 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.rhkb.cn/news/308406.html

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

相关文章

web自动化测试系列-selenium xpath定位方法详解(六)

1.xpath介绍 XPath 是一门在 XML 文档中查找信息的语言。XPath 用于在 XML 文档中通过元素和属性进行导航。而html中也应用了这种语言 &#xff0c;所以 &#xff0c;我们定位html页面元素时也会用到xpath这种方法 。 2.xpath定位方式 xpath主要通过以下四种方法定位 &#…

Windows系统本地部署Jupyter Notebook并实现公网访问编辑笔记

文章目录 1.前言2.Jupyter Notebook的安装2.1 Jupyter Notebook下载安装2.2 Jupyter Notebook的配置2.3 Cpolar下载安装 3.Cpolar端口设置3.1 Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 在数据分析工作中&#xff0c;使用最多的无疑就是各种函数、图表、…

基于深度学习的智能停车场车牌识别计费系统(完整程序+训练数据集+开题报告+论文))

摘要 本篇论文研究的是基于车牌识别技术的智能停车场管理系统&#xff0c;采用基于深度学习的车牌识别算法&#xff0c;通过卷积神经网络对车牌图像进行处理和分析&#xff0c;实现车牌字符的识别和车牌信息的提取。同时&#xff0c;本文还设计了一个智能停车场管理系统&#x…

RTSP/Onvif视频安防监控平台EasyNVR调用接口返回匿名用户名和密码的原因排查

视频安防监控平台EasyNVR可支持设备通过RTSP/Onvif协议接入&#xff0c;并能对接入的视频流进行处理与多端分发&#xff0c;包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等多种格式。平台拓展性强、支持二次开发与集成&#xff0c;可应用在景区、校园、水利、社区、工地等场…

[opencv]VideoWriter写出fourcc格式

fourcc支持的格式 fourcc全名Four-Character Codes&#xff0c;四字符代码&#xff0c;该编码由四个字符组成 cv2.VideoWriter_fourcc(O,O,O,O) cv2.VideoWriter_fourcc(*OOOO) 通常写法有上述两种形式&#xff0c;O代表一个字符&#xff0c;通常有 支持avi格式的有&#…

react17+18 中 setState是同步还是异步更新

在类组件中使用setState&#xff0c;在函数式组件中使用hooks的useState。 setstate目录 1. 类组件1.1 react 17版本1.2 react 18版本 2、函数式组件 1. 类组件 1.1 react 17版本 参考内容&#xff1a;第十一篇&#xff1a;setState 到底是同步的&#xff0c;还是异步的&…

Java 集合Collection

集合的体系 Collection的结构体系 List系列集合&#xff1a;添加的元素是有序的、可重复、有索引。Set系列集合&#xff1a;无序、不重复、无索引 HashSet&#xff1a;无序、不重复、无索引LinkedHashSet:有序、不重复、无索引TreeSet&#xff1a;按照大小默认升序排序、不重复…

数据结构--单链表

一.单链表的设计 1.单链表的结构定义: typedef struct Node{ int data;//数据域 struct Node* next;//后继指针 }Node,*List; 2.单链表的设计示意图: 3.注意: 单链表的最后一个节点的next域为NULL; 4.为什么要有一个头节点? 简单方便,不用传二级指针; 二.单链表的实现 …

韩顺平 | 零基础快速学Python(9~11) 排序查找

排序和查找 排序 排序&#xff1a;将多个数据按顺序排列。有冒泡、选择、插入、希尔、归并、快速、堆、计数、捅、基数排序。 冒泡排序 冒泡排序 Bubble Sorting&#xff1a;重复地走访需要排序地元素列表&#xff0c;依次比较相邻的元素&#xff0c;如果顺序错误就交换位置…

SSH穿透ECS访问内网RDS数据库

处于安全考虑&#xff0c;RDS一般只会允许指定的IP进行访问&#xff0c;而我们开发环境的IP往往是动态的&#xff0c;每次IP变动都需要去修改RDS的白名单&#xff0c;为我们的工作带来很大的不便。 那么如何去解决这个问题&#xff1f; 假如我们有一台ESC服务器&#xff0c;E…

简介:基于Web的产品3D

基于 Web 的产品 3D 通过可视化界面获得各种选项来个性化他们的产品&#xff0c;例如颜色、材料、尺寸、文字、徽标、零件等。 在过去几年中&#xff0c;随着 3D 建模和渲染软件的出现&#xff0c;3D 渲染现在更常用于营销和促销目的。设计师、制造商和营销人员使用 3D 产品渲…

【Kafka】Kafka 架构深入

Kafka 工作流程及文件存储机制 Kafka 中消息是以 topic 进行分类的&#xff0c;生产者生产消息&#xff0c;消费者消费消息&#xff0c;都是面向 topic 的。 topic 是逻辑上的概念&#xff0c;而 partition 是物理上的概念&#xff0c;每个 partition 对应于一个 log 文件&am…

11111111

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

MT3020 任务分配

思路&#xff1a;利用二分找到某个时间是满足“k个人可以完成” &#xff0c;并且时间最小。 因为尽量让后面的人做任务&#xff0c;所以从后往前排任务&#xff08;倒着分配&#xff09;。从后往前遍历任务&#xff0c;如果此人加上这个任务超出之前求得的时间&#xff0c;就…

逐步学习Go-sync.RWMutex(读写锁)-深入理解与实战

概述 在并发编程中&#xff0c;我们经常会遇到多个线程或协程访问共享资源的情况。为了保护这些资源不被同时修改&#xff0c;我们会用到"锁"的概念。 Go中提供了读写锁&#xff1a;sync.RWMutex。 sync.RWMutex是Go语言提供的一个基础同步原语&#xff0c;它是Rea…

Nikon | NEF格式图片批量转换为jpg格式

如何将nikon相机拍的NEF格式图片转换为jpg格式呢&#xff1f; 这里推荐一个在线转换的网址&#xff1a; https://picflow.com/convert/nef-to-jpg 添加图片后&#xff0c;可以批量选择图片&#xff0c;点击转换后即可进行下载

74HC595引脚图时序图工作原理

74HC595和74hc164一样是在单片机系统中常用的芯片之一他的作用就是把串行的信号转为并行的信号&#xff0c;常用在各种数码管以及点阵屏的驱动芯片&#xff0c; 使用74HC595可以节约单片机mcu的io口资源&#xff0c;用3个io就可以控制8个数码管的引脚&#xff0c;他还具有一定的…

[攻防世界]Reversing-x64Elf-100

1.查壳 无壳&#xff0c;ELF文件 2.用IDA64打开 找到关键部分 这里有坑&#xff0c;看清楚v3是长度为3数组&#xff0c;里面放三个字符串 3.脚本解密 v1"Dufhbmf" v2"pGimos" v3"ewUglpt" v4[v1,v2,v3] a1[0,0,0,0,0,0,0,0,0,0,0,0] for i …

乐趣Python——办公魔法:Word文件自动化

嘿&#xff0c;朋友们&#xff01;在这个办公小课堂中&#xff0c;我将为大家揭开一个神奇的秘密&#xff1a;Word文件自动化处理&#xff01; 通过这种魔法般的方式&#xff0c;我们可以大大提高办公效率&#xff0c;减少重复性工作。而Python作为我们的助手&#xff0c;将展现…

JavaSE-12笔记【集合1(+2024新)】

文章目录 1. 集合概述2.Collection2.1 Collection继承结构&#xff08;基于Java21&#xff09;2.2 Collection接口的常用方法2.3 Collection的遍历&#xff08;集合的通用遍历方式&#xff09;2.4 所有的有序集合都实现了SequencedCollection接口2.5 泛型2.5.1 如何判断是否可以…