手写操作系统 - 编译链接与运行

操作系统进入 32 位模式以后,我们后面的代码就是用 C 语言来写了,这样写起来会轻松很多也会快很多。但是我们需要了解 C 语言的本质和原理,后面会遇到很多问题,如果不理解其中的本质,有些问题我们查起来会很慢或者根本查不出来,很多同学可能也会因此而放弃。

语言分类介绍

我自己学过这么些客户端语言:汇编、C/C++、Java、kotlin、OC、swift、flutter 。刚开始做 Android 用的就是 Java 语言,基本要了我半条命,后面做跨平台做 iOS 学 OC 和 Swift 就是一遍上班一边学,当时就用了两周,一周学语言本身的原理,一周学语法,那就很快了。获得一个普世的智慧以后,对于语言那么一通应该就要百通。我们一起来看下:

首先计算机 CPU 是硬件晶体管做的,只能执行硬件指令也就是机器码指令,汇编是助记型语言基本是直接转机器码。所以汇编可以说是一切语言的基础,比如我们做 C/C++ 开发的,能看懂汇编代码才能真正了解 C/C++ 的本质和原理。

再往上走是 C/C++ 属于编译器语言,编译器帮我转汇编,汇编再转机器码,C/C++ 可以说是一切其他语言的基础了,比如 Java 和 kotlin 编译出来的 class 是跑在 Android/Java 虚拟机上的,虚拟机就是 C/C++ 和汇编来实现的,所以如果我们看不懂虚拟机源码,原则上不能说精通 Java 。还有像 flutter 的引擎就是 C/C++ 来写的。

再往上走像 Java 是虚拟机语言,依赖虚拟机来运行,python 是解释器语言,依赖解释器来运行,还有其他各种我不了解的语言,但是如果要我去上手,我也可以很有信心的说,只要给我两周时间,我应该就能搞了。

那一门语言值不值得我们花时间呢?再比如现在要我新开负责一个项目应该怎么选型呢?本质上其实还只要我们足够了解就能做好了,需要了解团队成员技术栈,需要了解语言本身的底层原理,需要了解未来大的方向趋势。我在 17 年那会就说了要大家学 kotlin 但是网上很多同学抨击我,但我不是拍脑袋,所以我坚信,所以我学了,虽然后来我也没讲,现在也没怎么用,但两周时间也不多。

编译器

以前写简历我都是写精通 X 语言,但是到现在我都只敢写了解 X 语言,为什么这么说呢?

语言的强大,来自于编译器的强大,来自于操作系统的强大,来自于虚拟机的强大,来自于引擎的强大,来自于系统库的强大,来自于第三方库的强大。所以我只能说了解 X 语言。

C 语言的编译流程是将人类可读的 C 源代码转换为计算机可执行的二进制程序的过程,主要分为 预处理 → 编译 → 汇编 → 链接 四个核心阶段。以下是每个阶段的详细说明,结合输入输出、关键操作和常见问题展开:

一、预处理(Preprocessing)

目标:处理源代码中的预编译指令(以 #开头的指令),生成无预编译指令的中间文件(.i或 .ii)。 输入:C 源文件(如 main.c)。 输出:预处理后的文本文件(如 main.i,C 语言为 .i,C++ 为 .ii)。

关键操作:

1.头文件展开(#include) 将 #include "xxx.h" 或 #include <xxx.h> 替换为对应头文件的实际内容(递归展开所有嵌套头文件)。

2.宏替换(#define) 将代码中的宏(如 #define PI 3.14)替换为对应的值(文本替换)。

3.条件编译(#ifdef/#ifndef/#endif) 根据预定义宏(如 #ifdef DEBUG)决定是否保留某段代码。

4.删除注释 删除代码中的单行注释(//)和多行注释(/* ... */)。

5.处理特殊指令 如 #pragma once(防止头文件重复包含)、#line(修改编译器报告的行号)等。

二、编译(Compilation)

目标:将预处理后的文本文件(.i)转换为汇编语言代码(.s),并完成语法检查、语义分析和代码优化。 输入:预处理后的文件(.i)。 输出:汇编语言源文件(.s,如 main.s)。

关键步骤:

1.词法分析(Lexical Analysis) 将预处理后的文本拆分为有意义的"词法单元"(Token),如关键字(int、if)、标识符(变量名、函数名)、运算符(+、=)、常量(123、"abc")等。

2.语法分析(Syntax Analysis) 根据词法单元构建 抽象语法树(AST),检查代码是否符合 C 语言语法规则(如括号是否匹配、语句是否完整)。

3.语义分析(Semantic Analysis) 检查代码的语义正确性(如类型是否匹配、变量是否声明)。

4.中间代码生成与优化 将 AST 转换为与机器无关的中间代码(如三地址码、静态单赋值形式),并进行优化(如常量折叠、死代码消除、循环展开等)。

三、汇编(Assembly)

目标:将汇编语言代码(.s)转换为机器语言的目标文件(.o或 .obj),生成二进制指令和数据。 输入:汇编语言文件(.s)。 输出:目标文件(.o,Linux/macOS;.obj,Windows)。

关键操作:

1.指令翻译 将汇编指令(如 mov eax, 5、call printf)转换为 CPU 可识别的机器码(二进制指令)。

2.符号表生成 目标文件中记录所有全局变量、函数的名称及其在内存中的相对地址(符号表),用于后续链接。

3.重定位信息记录 记录代码中引用的外部符号(如其他目标文件或库中的函数)的位置,以便链接器调整地址(重定位)。

四、链接(Linking)

目标:将多个目标文件(.o)和库文件(静态库 .a/.lib、动态库 .so/.dll)合并为一个可执行程序(如 a.out或 main.exe),解决符号引用问题。 输入:多个目标文件(.o)、库文件。 输出:可执行文件(如 main、main.exe)。

关键步骤:

1.符号解析(Symbol Resolution) 链接器遍历所有目标文件和库,解析代码中对函数、变量的引用(如 printf),找到其实际定义的位置(可能在当前目标文件或其他库中)。 2.重定位(Relocation) 将所有目标文件的代码段、数据段合并到可执行文件的对应段中,并调整符号的相对地址为实际内存地址(因为每个目标文件的地址默认从 0开始,需映射到统一内存空间)。

3.静态链接 vs 动态链接 静态链接:将库的代码直接复制到可执行文件中(如 .a库),生成的可执行文件独立运行(无需依赖外部库)。 动态链接:仅记录库的路径和符号信息(如 .so、.dll),运行时加载库(节省内存,多个程序共享同一库)。

完整编译流程示例(GCC)

以 main.c为例,完整流程命令如下:

css 复制代码
# 1. 预处理:生成 main.i
gcc -E main.c -o main.i

# 2. 编译:生成 main.s(汇编代码)
gcc -S main.i -o main.s

# 3. 汇编:生成 main.o(目标文件)
gcc -c main.s -o main.o

# 4. 链接:生成可执行文件 main(链接标准库)
gcc main.o -o main

main.c(源代码) → [预处理] → main.i(无指令文本) → [编译] → main.s(汇编代码) → [汇编] → main.o(目标文件) → [链接] → main(可执行文件)

相关推荐
Digitally7 小时前
5种将iPhone同步到iPhone的简单方法
ios·iphone
小趴菜82279 小时前
安卓接入Kwai广告源
android·kotlin
2501_916013749 小时前
iOS 混淆与 App Store 审核兼容性 避免被拒的策略与实战流程(iOS 混淆、ipa 加固、上架合规)
android·ios·小程序·https·uni-app·iphone·webview
程序员江同学10 小时前
Kotlin 技术月报 | 2025 年 9 月
android·kotlin
码农的小菜园11 小时前
探究ContentProvider(一)
android
时光少年12 小时前
Compose AnnotatedString实现Html样式解析
android·前端
hnlgzb13 小时前
安卓中,kotlin如何写app界面?
android·开发语言·kotlin
jzlhll12313 小时前
deepseek kotlin flow快生产者和慢消费者解决策略
android·kotlin
火柴就是我13 小时前
Android 事件分发之动态的决定某个View来处理事件
android
一直向钱13 小时前
FileProvider 配置必须针对 Android 7.0+(API 24+)做兼容
android