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

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

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

相关文章

基于Linux定时任务实现的MySQL周期性备份

1、创建备份目录 sudo mkdir -p /var/backups/mysql/database_name2、创建备份脚本 sudo touch /var/backups/mysql/mysqldump.sh# 用VIM编辑脚本文件&#xff0c;写入备份命令 sudo vim /var/backups/mysql/mysqldump.sh# 内如如下 #!/bin/bash mysqldump -uroot --single-…

下载好了annaconda,但是在创建一个新的Conda虚拟环境报错

文章目录 问题描述&#xff1a;解决方案1.生成一个配置文件 问题总结 问题描述&#xff1a; ProxyError(MaxRetryError(“HTTPSConnectionPool(host‘repo.anaconda.com’, port443): Max retries exceeded with url: /pkgs/pro/win-64/repodata.json.bz2 (Caused by ProxyErr…

系统架构最佳实践 -- 金融企业的资损防控

一、资损产生的原因 由于支付行业的特殊性与复杂性&#xff08;主要处理资金相关业务&#xff09;&#xff0c;支付公司处于资损的风口浪尖&#xff0c;最容易发生资损&#xff0c;可以说资损风险无处不在。 常规来说&#xff0c;资损原因主要可以分为以下三类&#xff1a; 1…

【鸿蒙开发】第二十章 Camera相机服务

1 简介 开发者通过调用Camera Kit(相机服务)提供的接口可以开发相机应用&#xff0c;应用通过访问和操作相机硬件&#xff0c;实现基础操作&#xff0c;如预览、拍照和录像&#xff1b;还可以通过接口组合完成更多操作&#xff0c;如控制闪光灯和曝光时间、对焦或调焦等。 2 …

浮点数的表示

王道考研ppt总结&#xff1a; 二、个人理解 浮点数解决的是定点数的位数局限&#xff0c;导致表示范围有限的问题 阶码&#xff1a;由阶符和数值部分组成&#xff0c;阶符为&#xff0c;小数点向左移动&#xff0c;否则向右移动&#xff1b;数值部分&#xff0c;是底数的几次幂…

【新版】系统架构设计师 - 知识点 - 面向对象开发方法

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 知识点 - 面向对象开发方法面向对象开发方法面向对象的分析需求模型分析模型 面向对象的设计 用例模型关系、UML事务关系、类的关系 架构 - 知识点 - 面向对象开发方法 面向对象开发方法 分析阶段…

多协议接入视频汇聚EasyCVR平台vs.RTSP安防视频EasyNVR平台:设备分组的区别

EasyCVR视频融合云平台则是旭帆科技TSINGSEE青犀旗下支持多协议接入的视频汇聚融合共享智能平台。平台可支持的接入协议比EasyNVR丰富&#xff0c;包括主流标准协议&#xff0c;有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海…

面试算法-164-K 个一组翻转链表

题目 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点内…

MySQL事务与事务原理

目录 事务 事务的四大特性ACID 事务隔离级别 事务原理 存储引擎 四大特性的保证 MVCC 事务链 ReadView 事务 事务指逻辑上的一组操作&#xff0c;组成这组操作的各个单元&#xff0c;要么全部成功&#xff0c;要么全部失败。 start transaction; -- 开启事务 或者 b…

【CDN(Content Delivery Network)】

文章目录 CDN&#xff08;Content Delivery Network&#xff09;视频流化服务和CDN&#xff1a;上下文多媒体: 视频存储视频的流化服务&#xff1a;多媒体流化服务&#xff1a;DASH流式多媒体技术3: DASH CDN&#xff08;Content Delivery Network&#xff09; 视频流化服务和…

kafka学习记录

文章目录 windows单机版kafka搭建步骤主题的增删改查操作消息的生产与消费 Windows集群版kafka搭建步骤 prettyZoo 尚硅谷Kafka教程&#xff0c;2024新版kafka视频&#xff0c;零基础入门到实战 【尚硅谷】Kafka3.x教程&#xff08;从入门到调优&#xff0c;深入全面&#xff0…

Electron+React 搭建桌面应用

创建应用程序 创建 Electron 应用 使用 Webpack 创建新的 Electron 应用程序&#xff1a; npm init electron-applatest my-new-app -- --templatewebpack 启动应用 npm start 设置 Webpack 配置 添加依赖包&#xff0c;确保可以正确使用 JSX 和其他 React 功能&#xff…

flask后端+网页前端:基于 socket.io 的双向通信和服务器部署

我想实现的效果是&#xff0c;我的服务器提供两个路由网址&#xff0c;网页A用于拍照、然后录音&#xff0c;把照片和录音传给服务器&#xff0c;服务器发射信号&#xff0c;通知另一个路由的网页B更新&#xff0c;把刚刚传来的照片和录音显示在网页上。 然后网页B用户根据这个…

每日一题(leetcode238):除自身以外数组的乘积--前缀和

不进阶是创建两个数组&#xff1a; class Solution { public:vector<int> productExceptSelf(vector<int>& nums) {int nnums.size();vector<int> left(n);vector<int> right(n);int mul1;for(int i0;i<n;i){mul*nums[i];left[i]mul;}mul1;for…

7 种实现 CSS 三角形的原理与方法 以及 三角形在网页设计中的作用

三角形在网页设计中不仅是图形设计的基本元素&#xff0c;更是实现视觉引导、空间构建、情绪传达、品牌塑造、性能优化以及创新表达的重要工具。其广泛应用和多功能性使其成为设计师手中不可或缺的设计语言组成部分。本文介绍了7种CSS实现三角形的方法。 CSS实现三角形主要有以…

Gradle 实战 - 命令行传递-ApiHug准备-工具篇-013

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace ApiHug …

VIO第7讲:VINS初始化与VINS系统

VIO第7讲&#xff1a;VINS初始化与VINS系统 文章目录 VIO第7讲&#xff1a;VINS初始化与VINS系统1 VINS初始化1.1 视觉初始化1.1.1 relativePose1.1.2 GlobalSFM与BA优化1.1.3 visualInitialAlign 1.2 VisualIMUAlignment1.2.1 视觉和IMU之间的联系1.2.2 视觉IMU对齐流程① 旋转…

【C++庖丁解牛】底层为红黑树结构的关联式容器--哈希容器(unordered_map和unordered_set)

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1. unordered系列关联式容…

风电机组中仍然装有电动机吗?

风电机组中确实装有电动机。虽然风电机组的主要功能是将风能转换为电能&#xff0c;但在其启动和运行过程中&#xff0c;电动机发挥着不可或缺的作用。 在风电机组的启动阶段&#xff0c;电动机负责提供初始的启动动力。由于风力发电的特性&#xff0c;风电机组并不能在任意风…

乐趣Python——文件与数据:挥别乱糟糟的桌面

各位朋友们&#xff0c;今天我们要开启一场非凡的冒险——进入文件操作的世界&#xff01;你知道吗&#xff0c;在你的电脑里&#xff0c;有一个叫做“文件系统”的迷宫&#xff0c;里面藏着各种各样的文件和文件夹&#xff0c;它们就像是迷宫中的宝藏。但有时候&#xff0c;这…