当前位置: 首页 > news >正文

编译器的相关知识(入门时著)

作用:将高级语言(c#、python)程序翻译成机器码(二进制数据)。

工作流程:预处理——>编译(词法、语法、语义分析,生成中间码/优化)——>汇编——>链接

我做一个便于理解的比喻,有兴趣请先移步下方阅读秋鳞小故事

https://blog.csdn.net/QL_SD/article/details/151440212?fromshare=blogdetail&sharetype=blogdetail&sharerId=151440212&sharerefer=PC&sharesource=QL_SD&sharefrom=from_linkhttps://blog.csdn.net/QL_SD/article/details/151440212?fromshare=blogdetail&sharetype=blogdetail&sharerId=151440212&sharerefer=PC&sharesource=QL_SD&sharefrom=from_link

=========================================================================

一、具体操作

(一)预处理(宏展开、条件编译、处理预定义宏、删除注释、处理编译器指令)

1.宏展开

这是预处理器的核心功能之一。它处理所有以 #define 定义的宏。

对象式宏:简单的文本替换。
// 源代码
#define PI 3.14159
#define BUFFER_SIZE 1024double circumference = 2 * PI * radius;
char buffer[BUFFER_SIZE];// 预处理后
double circumference = 2 * 3.14159 * radius; // PI被替换
char buffer[1024]; // BUFFER_SIZE被替换
函数式宏:带参数的宏。
// 源代码
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))int x = 10, y = 20;
int z = MAX(x, y);
int sq = SQUARE(5);// 预处理后
int x = 10, y = 20;
int z = ((x) > (y) ? (x) : (y)); // 宏被展开,参数被替换
int sq = ((5) * (5)); // 宏被展开,参数被替换

2.条件编译

根据条件决定哪些代码块被包含在编译中,哪些被忽略。这是实现跨平台、调试、功能开关的关键。

#if, #elif, #else, #endif:根据条件判断。
#define VERSION 2#if VERSION == 1printf("Running version 1\n");
#elif VERSION == 2printf("Running version 2\n"); // 只有这部分代码会被保留
#elseprintf("Running unknown version\n");
#endif
#ifdef, #ifndef:检查宏是否被定义(常用于头文件保护符和功能开关)。
// 头文件保护符:防止头文件被多次包含
#ifndef MY_HEADER_H // 如果MY_HEADER_H未定义
#define MY_HEADER_H // 则定义它,并包含以下内容// 头文件的真实内容...
void some_function();#endif // MY_HEADER_H// 功能开关
#define DEBUG_MODE // 注释掉这行即可关闭调试信息#ifdef DEBUG_MODE#define DEBUG_PRINT(msg) printf("DEBUG: %s\n", msg)
#else#define DEBUG_PRINT(msg) // 定义为空,编译后什么也不做
#endif

3.处理预定义宏

编译器本身会预定义一些宏,它们在预处理时会被展开为特定的值,非常有用。

①常见预定义宏
printf("File: %s\n", __FILE__);     // 当前源代码文件名
printf("Line: %d\n", __LINE__);     // 当前行号
printf("Date: %s\n", __DATE__);     // 编译日期 (格式 "MMM DD YYYY")
printf("Time: %s\n", __TIME__);     // 编译时间 (格式 "HH:MM:SS")
printf("Function: %s\n", __func__); // 当前函数名 (C99标准)
// 检查操作系统
#ifdef __linux__// Linux-specific code
#elif defined(_WIN32)// Windows-specific code
#endif

4.删除注释

预处理器的首要任务之一就是移除所有注释,因为注释对编译器毫无意义。

// 这是一个单行注释
int a = 10; /* 这是一个多行注释 */
int b = 20; // 另一个注释int a = 10; 
int b = 20; 

5. 处理其他编译器指令

#error:强制产生一个编译错误并输出消息。
#ifndef REQUIRED_MACRO
#error "REQUIRED_MACRO is not defined! Please define it." // 编译将在此处停止
#endif
#pragma:向编译器传递实现特定的指令。
#pragma once // 非标准但广泛支持的头文件保护符,作用类似#ifndef/#define
#pragma message("Compiling this file...") // 在编译输出中显示一条消息
#pragma warning(disable: 4996) // (MSVC) 禁用特定编号的警告
#line:改变 __LINE__ 和 __FILE__ 宏的值。
//改变 __LINE__ 
#include <stdio.h>int main() {printf("This is line %d in file %s\n", __LINE__, __FILE__);#line 100 // 从这里开始,行号被重置为100printf("This is line %d in file %s\n", __LINE__, __FILE__);printf("This is line %d in file %s\n", __LINE__, __FILE__);return 0;
}//打印
This is line 4 in file example.c
This is line 100 in file example.c
This is line 101 in file example.c//=====================================================
//同时改变行号和文件名
1:  #include <stdio.h>
2:  
3:  int main() {
4:      printf("Error location: %s:%d\n", __FILE__, __LINE__);
5:      
6:  #line 1 "fake_file.h" // 指令生效!
7:      printf("Error location: %s:%d\n", __FILE__, __LINE__);
8:      
9:      int x = 5;
10:     printf("Error location: %s:%d\n", __FILE__, __LINE__);
11:     
12:     int y = x / 0; // 除零错误!
13:     
14:     return 0;
15: }//错误发生在原始代码的第12行,但因为我们用 #line 设置了文件名和行号。
//编译器报告错误时,会显示 fake_file.h(5),而不是真正的文件名和行号。//编译时的错误信息:
fake_file.h(5): error C2124: divide or mod by zero/*
报错信息分解:
①fake_file.h:编译器报告的发生错误的文件名。在代码中使用了 #line 1 "fake_file.h" 指令。这条指令强制编译器将它之后处理的所有代码都“当作”是来自一个名为 fake_file.h 的虚拟文件。所以,当错误发生时,编译器诚实地报告了它“认为”的当前文件名。
②(5):编译器报告的发生错误的行号。为什么是5:这是由 #line 1 "fake_file.h" 指令设置的。该指令将下一行(第一个 printf)的逻辑行号设置为 1。下一行(int x = 5;)的逻辑行号就是 2。再下一行(第二个 printf)的逻辑行号是 3。注释行 // 这行代码会产生... 的逻辑行号是 4。出错的行 int y = x / 0; 的逻辑行号就是 5。
③error C2124: divide or mod by zero:错误的类型和描述。这是Microsoft Visual C++编译器的特定错误代码(C2124),表示在编译时检测到了除数为零的运算(除法/或取模%)。这是一个致命错误,会导致编译失败。
*/

结果:

输出一个“纯净”的、没有注释和宏定义的文本文件(通常为.i文件)。

(二)编译

词法分析(输出Token流):将每句代码拆分成基础词法单元。

将“int a = 10 + 5;”拆分成" int ","a "," = "," 10 "," + "," 5 " " ; "。如果出现词法错误就会报错

②语法分析(输出AST(抽象语法树)):检查“单词”是否符合语法规则,并生成一棵“语法树”。

检查“int a = 10 + 5;”符不符合C语言的语法(类型 变量名 = 表达式;)不符合的话就会语法报错。(下图就是“语法树”)

    Declaration/    |    \type  name  initializer(int)  (a)     |BinaryExpr/   |   \op   lhs  rhs(+)  (10) (5)

③语义分析(装饰AST:确认类型等):检查语句是否有意义。

检查“int a = b + c”,会去检查“a”、“b”是否已经声明,类型是否正确。如果类型不能进行相关运算或者未声明,会语义报错。

④生成中间表示/优化:生成非常接近机器码,但是又不依赖具体PCU的中间表示(中间表示不是),并且将进行各种优化。

" int a = 10 + 5 "直接优化成"int a = 15",再之后给人看的变量也会不存在,而是直接给立即数“15”分配一个地址空间,之后程序访问“a”值就是直接去立即数“15”的地址

⑤代码生成:

将优化后的中间表示转换为目标相关的汇编代码(转换成不同系统架构适用的汇编代码:ARM、X86)

结果:输出汇编代码(.s文件)

(三)汇编

将经过编译的代码翻译成机器码(二进制指令)

结果:输出目标文件(通常为.o.obj文件),里面已经是机器码了,但还不完整。

(四)链接

链接各种文件做集成:

        一个程序通常由多个源文件(.c)编译成多个目标文件(.o),并且会用到标准库(如printf函数)。链接器的工作就是把所有这些零散的目标文件拼凑在一起。解决它们之间的相互引用问题(比如你在main.c里调用了function.c里的一个函数,链接器负责把这个调用关系连接上)。把标准库的代码也“链接”进来。

输出输出:最终的可执行文件(如Windows的.exe,Linux的elf,单片机的.hex.bin

二、C和python的编译器的不同

(1)C的流程:

C 源代码 -> C#编译器 -> IL 中间代码 -> (运行时) -> JIT 编译器 -> 本地机器码 -> CPU 执行

c是编译完所有代码之后才会执行。所以编译的时候会比代码运行时的报错少,比如10÷0可以被编译通过,只会在代码运行到对应位置,才会因为语法错误,导致程序停止运行

(2)python的流程:

Python 源代码 -> Python 编译器 -> 字节码 -> (运行时) -> PVM 解释器 -> C 函数 -> CPU 执行

python是编译一句执行一句,所以当编译10÷0之后就会立刻执行,然后就报错停止运行。


文章转载自:

http://fNnAX6Bh.rqrxh.cn
http://qhza4nEq.rqrxh.cn
http://8F1zc7wE.rqrxh.cn
http://wEUaPpm1.rqrxh.cn
http://LzWPpyXs.rqrxh.cn
http://yBWKaAST.rqrxh.cn
http://O4z13is8.rqrxh.cn
http://7T5ffITK.rqrxh.cn
http://OTIDOHzP.rqrxh.cn
http://GjimHBmG.rqrxh.cn
http://x6Q4Fz1z.rqrxh.cn
http://7jjiTLlM.rqrxh.cn
http://imBZhLHQ.rqrxh.cn
http://0l2LdRhm.rqrxh.cn
http://K7iCdYLv.rqrxh.cn
http://kXsMGSvR.rqrxh.cn
http://qnRz7Ylb.rqrxh.cn
http://oCd9jzY2.rqrxh.cn
http://ZZ4Z0jNM.rqrxh.cn
http://z1z6l310.rqrxh.cn
http://TXwBCJVF.rqrxh.cn
http://ayPd01wf.rqrxh.cn
http://JDqLyzGq.rqrxh.cn
http://FBQmRn2b.rqrxh.cn
http://f9IArIxl.rqrxh.cn
http://RTV4QI7g.rqrxh.cn
http://kzyqbwJ7.rqrxh.cn
http://bdqpjPCX.rqrxh.cn
http://1Yg4nIhe.rqrxh.cn
http://hbF2cBAU.rqrxh.cn
http://www.dtcms.com/a/379228.html

相关文章:

  • 开始 ComfyUI 的 AI 绘图之旅-Flux.1 ControlNet (十)
  • 企业微信内部应用js-sdk使用流程
  • Java Spring Boot常见异常全解析:原因、危害、处理与防范
  • Qt加载百度地图详细流程(附带报错解决方法)
  • 3D渲染时GPU内存不足解决措施
  • MySQL什么操作会加锁?
  • 中州养老:华为云设备管理接口开发全流程
  • 探讨图片以Base64存数据库的合理性
  • MoonBit 再次走进清华:张宏波受邀参加「思源计划」与「程序设计训练课」
  • RabbitMQ如何实现消息的持久化?
  • Crawlergo安装全流程
  • 完全背包问题 - 动态规划最优解法(Java实现)
  • 如何选择合适的双轴倾角传感器厂家提升水平监测准确性?
  • 洛谷PP5318 查找文献 (深度搜索与广度搜索)详解
  • 手机云服务是什么意思?
  • Linux 基础操作全攻略:从文件解压到服务器管理
  • web:ts的类类型
  • 初识StarRocks
  • linux常见的基础命令及其作用
  • 12 Prompt 模板化与参数化
  • 自动化车间无线安灯呼叫系统解决方案
  • Oracle APEX 如何运行页面时跳过登录页
  • list容器
  • Docker Compose:轻松管理多容器应用
  • 云蝠智能大模型呼叫新模型上线,拥抱AGI
  • 网站SEO内部优化一般包括哪些内容和方法
  • 18j621-3通风天窗图集pdf(免费高清版)
  • 以下是UniApp启动速度优化的深度方案
  • GoogLeNet实战:用PyTorch实现经典Inception模块
  • verilog中task的使用