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

作用:将高级语言(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 定义的宏。

对象式宏:简单的文本替换。
cs 复制代码
// 源代码
#define PI 3.14159
#define BUFFER_SIZE 1024

double circumference = 2 * PI * radius;
char buffer[BUFFER_SIZE];

// 预处理后
double circumference = 2 * 3.14159 * radius; // PI被替换
char buffer[1024]; // BUFFER_SIZE被替换
函数式宏:带参数的宏。
cs 复制代码
// 源代码
#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**:根据条件判断。
cs 复制代码
#define VERSION 2

#if VERSION == 1
    printf("Running version 1\n");
#elif VERSION == 2
    printf("Running version 2\n"); // 只有这部分代码会被保留
#else
    printf("Running unknown version\n");
#endif
②**#ifdef, #ifndef**:检查宏是否被定义(常用于头文件保护符和功能开关)。
cs 复制代码
// 头文件保护符:防止头文件被多次包含
#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.处理预定义宏

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

①常见预定义宏
cs 复制代码
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.删除注释

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

cs 复制代码
// 这是一个单行注释
int a = 10; /* 这是一个
              多行注释 */
int b = 20; // 另一个注释

int a = 10; 
int b = 20; 

5. 处理其他编译器指令

①**#error**:强制产生一个编译错误并输出消息。
cs 复制代码
#ifndef REQUIRED_MACRO
#error "REQUIRED_MACRO is not defined! Please define it." // 编译将在此处停止
#endif
②**#pragma**:向编译器传递实现特定的指令。
cs 复制代码
#pragma once // 非标准但广泛支持的头文件保护符,作用类似#ifndef/#define
#pragma message("Compiling this file...") // 在编译输出中显示一条消息
#pragma warning(disable: 4996) // (MSVC) 禁用特定编号的警告
③**#line** :改变 __LINE____FILE__ 宏的值。
cs 复制代码
//改变 __LINE__ 
#include <stdio.h>

int main() {
    printf("This is line %d in file %s\n", __LINE__, __FILE__);
    
#line 100 // 从这里开始,行号被重置为100
    printf("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之后就会立刻执行,然后就报错停止运行。

相关推荐
宝儿6528 小时前
用vscode做一个简单的扫雷小游戏
ide·vscode·编辑器
金山电脑医生9 小时前
Sublime Text 4 下载 + 安装 + 汉化全流程教程(图文保姆级指南)
编辑器·sublime text
讓丄帝愛伱15 小时前
Vim核心操作
linux·编辑器·vim
jerryinwuhan1 天前
VIM和Linux命令速查表
linux·编辑器·vim
jedi-knight2 天前
Vscode+CMake编译时出现中文乱码
ide·vscode·编辑器
神洛华2 天前
YDWE编辑器系列教程三:触发编辑器
游戏·编辑器
egoist20232 天前
[linux仓库]深入解析Linux动态链接与动态库加载:理解背后的原理与技巧
linux·服务器·编辑器·动态库·got
神洛华2 天前
YDWE编辑器系列教程一:编辑器界面
游戏·编辑器
许商2 天前
【stm32】cmake构建vscode开发环境
ide·vscode·编辑器
Siren_dream3 天前
CSDN Markdown 编辑器快捷键大全
编辑器