docs(Philosophy): 添加第 0.1,0.2 节
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
---
|
||||
title: "编程哲学导论"
|
||||
subtitle: "Programming Philosophy - Introduction"
|
||||
version: "1.0"
|
||||
category: "哲学篇 · 导论"
|
||||
order: 0.1
|
||||
tags: [编程, 哲学, 导论, 基础概念]
|
||||
---
|
||||
|
||||
# 0.1 编程哲学导论
|
||||
|
||||
> **本节导读**:从最本质的角度理解"编程"的含义,建立对代码、编译器、程序的核心认知,并初步了解程序设计的基本哲学思想。
|
||||
|
||||
---
|
||||
|
||||
## 一、什么是编程?
|
||||
|
||||
### 1.1 词源解析
|
||||
|
||||
| 汉字 | 含义 | 词性 | 说明 |
|
||||
|:----:|------|:----:|------|
|
||||
| **编** | 编写 | 动词 (verb) | 编写代码的动作 |
|
||||
| **程** | 程序 / 程式 | 名词 (noun) | 具有格式和顺序的结构 |
|
||||
|
||||
### 1.2 核心定义
|
||||
|
||||
> **编程 (Programming)**:按照一定的 **格式** 与 **顺序** 编写代码的过程。
|
||||
|
||||
我们编程的核心目标,是对各种各样的 **文件** 进行操作。
|
||||
|
||||
#### 直观类比
|
||||
|
||||
可以将编程与 **烹饪** 做类比:
|
||||
|
||||
| 烹饪 | 编程 |
|
||||
|------|------|
|
||||
| 菜谱 (格式 + 顺序) | 代码 / 算法 |
|
||||
| 食材 (原料) | 数据 / 文件 |
|
||||
| 菜肴 (最终产出) | 可执行程序 |
|
||||
| 厨师 (执行者) | CPU / 计算机 |
|
||||
|
||||
---
|
||||
|
||||
## 二、核心概念
|
||||
|
||||
### 2.1 什么是代码?
|
||||
|
||||
**代码 (Code)** 本质上是 **文本文件**,即由 ASCII 字符组成的纯文本。
|
||||
|
||||
#### 常见代码文件扩展名
|
||||
|
||||
| 语言 | 扩展名 | 说明 |
|
||||
|------|--------|------|
|
||||
| C 语言 | `.c` | C 源文件 |
|
||||
| C 语言 | `.h` | C 头文件 |
|
||||
| C++ | `.cpp` | C++ 源文件 |
|
||||
| C++ | `.hpp` | C++ 头文件 |
|
||||
| C++ | `.cc` / `.cxx` | C++ 源文件(替代写法) |
|
||||
|
||||
> **NOTE**:`.c` / `.h` 通常用于 C 语言代码;`.cpp` / `.h` / `.cc` / `.cxx` / `.hpp` 通常用于 C++ 代码。
|
||||
|
||||
> **NOTE**:只要是合法的 C 或 C++ 的 ASCII 文本(无论使用何种扩展名),都可以被称为 **代码**。
|
||||
|
||||
#### 代码示例
|
||||
|
||||
**示例 — C 语言 "Hello, World"**:
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("Hello, World!\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
> **NOTE**:以上代码就是一个纯 ASCII 文本文件,保存为 `main.c` 后,它就是一段 **代码**。无论你将扩展名改为 `.txt`、`.abc` 甚至不加扩展名,只要内容合法,它依然是代码——只是编译器可能无法自动识别而已。
|
||||
|
||||
---
|
||||
|
||||
### 2.2 什么是编译器?
|
||||
|
||||
**编译器 (Compiler)** 是将 **代码** 转换为 **可执行文件** 的工具。
|
||||
|
||||
#### 直观类比
|
||||
|
||||
可以将编译器类比为 **翻译官**:
|
||||
|
||||
| 翻译官 | 编译器 |
|
||||
|--------|--------|
|
||||
| 输入:外文原文 | 输入:源代码 (C/C++) |
|
||||
| 处理:语法分析、语义理解 | 处理:词法/语法/语义分析 |
|
||||
| 输出:本国语言译文 | 输出:机器码 (可执行文件) |
|
||||
|
||||
如果原文有语法错误,翻译官会报错并指出问题位置 —— 编译器也是如此。
|
||||
|
||||
#### 输入与输出
|
||||
|
||||
```
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ Input │ →→→→ │ Output │
|
||||
│ │ │ │
|
||||
│ File, Code │ Compiler │ Executable File, │
|
||||
│ (ASCII Text) │ │ Linker Output │
|
||||
└─────────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
#### 编译流程全景图
|
||||
|
||||
以 Windows 上的编译流程为例:
|
||||
```
|
||||
Source File(s)
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Preprocessor │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Compiler │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Assembler │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Linker │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
Executable File (.exe)
|
||||
```
|
||||
|
||||
> **REFERENCE**:关于编译流程各阶段的详细说明,请参阅 [→ 0.2 编程基础](./0.2%20Philosophy%20-%20Basic.md#三编译流程详解)
|
||||
|
||||
---
|
||||
|
||||
### 2.3 什么是程序?
|
||||
|
||||
**程序 (Program)** 是经过编译后生成的可执行文件,是计算机能够直接理解和执行的指令集合。
|
||||
|
||||
#### 直观类比
|
||||
|
||||
| 概念 | 类比 |
|
||||
|------|------|
|
||||
| **源代码** | 乐谱 (人可读,但不能直接发声) |
|
||||
| **编译器** | 钢琴家 (将乐谱"翻译"为按键动作) |
|
||||
| **程序/可执行文件** | 录好的音频文件 (可直接播放) |
|
||||
| **运行程序** | 播放录音 (计算机执行指令) |
|
||||
|
||||
#### 示例
|
||||
|
||||
```
|
||||
main.c (Source Code) --[Compile]--> main.exe (Program) --[Run]--> Output
|
||||
Human Readable Machine Executable User Sees
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、基本设计哲学
|
||||
|
||||
> 以下是我对程序基本设计哲学的理解与总结:
|
||||
|
||||
### 3.1 抽象 (Abstraction)
|
||||
|
||||
用代码的方式 **描述问题**,而不纠结于实现细节。
|
||||
|
||||
- 将复杂现实简化为可计算的模型
|
||||
- 关注 **"做什么"** 而非 **"怎么做"**
|
||||
- 通过接口隐藏底层复杂性
|
||||
|
||||
#### 示例
|
||||
|
||||
**场景:发送一封电子邮件**
|
||||
|
||||
| 层级 | 抽象程度 | 你需要关心的事 |
|
||||
|:----:|----------|----------------|
|
||||
| **用户层** | 最高 | 收件人地址、邮件标题、正文内容 |
|
||||
| **应用层** | 高 | 调用 `smtp.send(to, subject, body)` |
|
||||
| **协议层** | 中 | SMTP 握手、HELO/EHLO、DATA 命令 |
|
||||
| **传输层** | 低 | TCP 三次握手、数据包分片与重组 |
|
||||
| **物理层** | 最低 | 电信号 / 光信号 / 无线电波 |
|
||||
|
||||
**抽象的意义**:当你写 `print("Hello")` 时,你不需要知道显卡如何渲染像素、操作系统如何调度显示驱动——这就是抽象的力量。
|
||||
|
||||
### 3.2 模块化 (Modularization)
|
||||
|
||||
将复杂问题 **分解** 为多个相对独立的子问题,每个子问题构成一个 **模块**。
|
||||
|
||||
```
|
||||
Complex Problem
|
||||
|
|
||||
+---> Module A ---> Sub-problem A
|
||||
+---> Module B ---> Sub-problem B
|
||||
+---> Module C ---> Sub-problem C
|
||||
```
|
||||
|
||||
**示例 — 开发一个计算器程序**:
|
||||
|
||||
```
|
||||
Calculator App
|
||||
|
|
||||
+--- [input_module] -- User input parsing
|
||||
+--- [calc_module] -- Arithmetic logic
|
||||
+--- [display_module] -- Result formatting & display
|
||||
+--- [history_module] -- History record management
|
||||
```
|
||||
|
||||
**优势**:
|
||||
- 降低复杂度:每个模块只关注一件事
|
||||
- 提高代码复用性:`calc_module` 可被其他项目引用
|
||||
- 便于团队协作开发:不同成员负责不同模块
|
||||
- 方便测试与调试:可单独对 `calc_module` 进行单元测试
|
||||
|
||||
### 3.3 封装 (Encapsulation)
|
||||
|
||||
将模块的 **实现细节隐藏** 起来,仅 **暴露必要的接口** 给外部使用。
|
||||
|
||||
```
|
||||
┌──────────────────────────────┐
|
||||
│ [ Module ] │
|
||||
│ │
|
||||
│ ┌───────────────────┐ │ <- Public API
|
||||
│ │ Interface │ │
|
||||
│ └────────┬──────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────▼──────────┐ │
|
||||
│ │ Private │ │ <- Hidden Details
|
||||
│ └───────────────────┘ │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
**示例 — 汽车驾驶**:
|
||||
|
||||
| 封装层 | 对外暴露(接口) | 隐藏细节 |
|
||||
|--------|------------------|----------|
|
||||
| 方向盘、油门、刹车 | 驾驶员可操作 | 引擎燃烧原理、变速箱齿轮比 |
|
||||
| 函数 `sqrt(x)` | 输入 x,输出根号 x | 牛顿迭代法的具体计算过程 |
|
||||
|
||||
```c
|
||||
// math_utils.c -- 实现细节
|
||||
static double newton_sqrt(double n) // static: 仅内部可见性
|
||||
{
|
||||
// 牛顿迭代法的具体实现... 外部不能直接调用此函数
|
||||
double guess = n / 2.0;
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
guess = (guess + n / guess) / 2.0;
|
||||
}
|
||||
|
||||
return guess;
|
||||
}
|
||||
|
||||
// math_utils.c -- 对外暴露的接口
|
||||
// 仅暴露必要的接口,隐藏实现细节
|
||||
// 例如:sqrt(9) = 3.0, sqrt(16) = 4.0
|
||||
double sqrt(double n)
|
||||
{
|
||||
if (n < 0) return 0.0; // 内部校验逻辑,负数返回0
|
||||
|
||||
return newton_sqrt(n);
|
||||
}
|
||||
```
|
||||
|
||||
**原则**:
|
||||
- 信息隐藏 (Information Hiding)
|
||||
- 最小知识原则 (Principle of Least Knowledge)
|
||||
- 高内聚、低耦合
|
||||
|
||||
---
|
||||
|
||||
## 本节小结
|
||||
|
||||
| 概念 | 核心要点 |
|
||||
|------|----------|
|
||||
| **编程** | 按照格式顺序编写代码,本质是对文件的操作 |
|
||||
| **代码** | ASCII 文本文件,具有约定扩展名规范 |
|
||||
| **编译器** | 将代码转换为可执行文件的工具,经历预处理→编译→汇编→链接 |
|
||||
| **程序** | 编译后的可执行指令集 |
|
||||
| **抽象** | 关注"做什么",忽略实现细节 |
|
||||
| **模块化** | 分而治之,降低复杂度 |
|
||||
| **封装** | 隐藏实现,暴露接口 |
|
||||
|
||||
---
|
||||
|
||||
> **上一节**:无(起始章节)
|
||||
> **下一节**:[→ 0.2 编程基础](./0.2%20Philosophy%20-%20Basic.md)
|
||||
@@ -0,0 +1,464 @@
|
||||
---
|
||||
title: "编程基础"
|
||||
subtitle: "Programming Philosophy - Basic Concepts"
|
||||
version: "1.0"
|
||||
category: "哲学篇 · 基础"
|
||||
order: 0.2
|
||||
tags: [编程, 哲学, 基础, 编译, 规范]
|
||||
prev: "./0.1%20Philosophy%20-%20Intro.md"
|
||||
next: null
|
||||
---
|
||||
|
||||
# 0.2 编程基础
|
||||
|
||||
> **本节导读**:掌握编程开发的规范体系,深入理解编译器的工作原理与完整编译流程。
|
||||
---
|
||||
|
||||
## 一、开发规范体系
|
||||
|
||||
> 规范是团队协作的基石,良好的编码习惯是程序员的基本素养。
|
||||
|
||||
### 1.1 项目规范
|
||||
|
||||
<!-- TODO: 待补充项目规范内容 -->
|
||||
|
||||
- [ ] 目录结构规范
|
||||
- [ ] 配置管理规范
|
||||
- [ ] 版本控制规范
|
||||
|
||||
**示例 — 推荐的 C 项目目录结构**:
|
||||
|
||||
```
|
||||
my_project/
|
||||
|-- include/ # 头文件 (.h)
|
||||
| +-- utils.h
|
||||
|-- src/ # 源文件 (.c)
|
||||
| +-- main.c
|
||||
| +-- utils.c
|
||||
|-- lib/ # 第三方库
|
||||
|-- build/ # 编译输出(不纳入版本控制)
|
||||
|-- docs/ # 项目文档
|
||||
+-- README.md # 项目说明
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.2 命名规范
|
||||
|
||||
- [ ] 变量命名规则
|
||||
- [ ] 函数命名规则
|
||||
- [ ] 文件命名规则
|
||||
|
||||
**示例 — 常见命名风格对比**:
|
||||
|
||||
| 风格 | 示例 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| `snake_case` | `user_name`, `file_path` | C 语言变量 / 函数 |
|
||||
| `UPPER_SNAKE` | `MAX_BUFFER`, `PI` | 宏常量 / 枚举 |
|
||||
| `camelCase` | `userName`, `filePath` | Java / JavaScript 变量 |
|
||||
| `PascalCase` | `ClassName`, `StructName` | 类型 / 结构体名称 |
|
||||
|
||||
```c
|
||||
// Good: 符合 C 语言命名惯例
|
||||
int user_count = 0;
|
||||
#define MAX_LENGTH 256
|
||||
void calculate_sum(int a, int b);
|
||||
|
||||
// Bad: 混用风格、含义模糊
|
||||
int x = 0; // 含义不明
|
||||
#define ML 256 // 缩写无法理解
|
||||
void CS(int a, int B); // 函数名无意义
|
||||
```
|
||||
---
|
||||
|
||||
### 1.3 代码规范
|
||||
|
||||
- [ ] 缩进与格式
|
||||
- [ ] 注释规范
|
||||
- [ ] 错误处理规范
|
||||
|
||||
**示例 — 良好 vs 不良代码对比**:
|
||||
|
||||
```c
|
||||
// --- Bad: 无缩进、无注释、魔法数字 ---
|
||||
int f(int r){return 3.14*r*r;}
|
||||
|
||||
// ---
|
||||
// Good: 规范缩进、有意义的命名、清晰注释
|
||||
|
||||
// Calculate the area of a circle with given radius.
|
||||
// @param radius The radius of the circle (must be >= 0)
|
||||
// @return The area of the circle
|
||||
double calculate_circle_area(double radius)
|
||||
{
|
||||
const double PI = 3.14159265;
|
||||
|
||||
if (radius < 0) {
|
||||
return 0.0; // Invalid input guard
|
||||
}
|
||||
|
||||
return PI * radius * radius;
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## 二、编译器概述
|
||||
|
||||
编译器是将人类可读的 **源代码** 转换为机器可执行的 **目标代码** 的程序。完整的编译过程包含四个主要阶段:
|
||||
|
||||
| 阶段 | 工具 | 输入 | 输出 | 核心任务 |
|
||||
|:----:|------|------|------|----------|
|
||||
| ① 预处理 | Preprocessor | `.c` / `.h` | `.i` | 宏展开、头文件包含、条件编译 |
|
||||
| ② 编译 | Compiler | `.i` | `.s` | 语法分析、语义分析、代码生成 |
|
||||
| ③ 汇编 | Assembler | `.s` | `.o` | 汇编代码转机器码 |
|
||||
| ④ 链接 | Linker | `.o` | `.exe` | 符号解析、地址重定位 |
|
||||
---
|
||||
|
||||
## 三、编译流程详解
|
||||
|
||||
### 3.1 预处理阶段 (Preprocessing)
|
||||
|
||||
> 预处理器的任务是对源文件进行文本层面的替换和处理,生成 **预处理后的源代码**。
|
||||
|
||||
#### 操作命令
|
||||
|
||||
```bash
|
||||
# 查看预处理结果
|
||||
g++ -E main.c -o main.i
|
||||
```
|
||||
|
||||
#### 预处理步骤详解
|
||||
|
||||
| 步序 | 操作 | 说明 | 示例 |
|
||||
|:----:|------|------|------|
|
||||
| 1 | 处理 `#include` | 包含指定的头文件内容 | `#include <stdio.h>` |
|
||||
| 2 | 展开 `#define` | 进行宏替换 | `#define PI 3.14` |
|
||||
| 3 | 条件编译 | 根据 #ifdef/#ifndef 等决定是否编译某段代码 | `#ifndef HEADER_H` |
|
||||
| 4 | 删除注释 | 移除所有 `//` 和 `/* */` 注释 | — |
|
||||
|
||||
> **TIP**:预处理后的 `.i` 文件通常非常大,因为包含了所有头文件的展开内容。
|
||||
|
||||
#### 预处理前后对比
|
||||
|
||||
**输入 (main.c)**:
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
|
||||
#define MAX_SIZE 100
|
||||
#define SQUARE(x) ((x) * (x))
|
||||
|
||||
int main()
|
||||
{
|
||||
int arr[MAX_SIZE];
|
||||
int val = SQUARE(5);
|
||||
|
||||
printf("Value: %d\n", val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**输出 (main.i — 部分截取)**:
|
||||
|
||||
```c
|
||||
// #include <stdio.h> 被展开为上千行 stdio 的实际内容...
|
||||
// 以下仅为宏展开部分:
|
||||
|
||||
int main()
|
||||
{
|
||||
int arr[100]; // MAX_SIZE -> 100
|
||||
int val = ((5) * (5)); // SQUARE(5) 宏展开
|
||||
|
||||
printf("Value: %d\n", val); // 注释保留(未删除前)
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
> **NOTE**:观察 `SQUARE(5)` 如何被替换为 `((5)*(5))` —— 这就是 **纯文本替换**,不做任何计算。
|
||||
---
|
||||
|
||||
### 3.2 编译阶段 (Compilation)
|
||||
|
||||
> 编译器将预处理后的代码进行词法、语法、语义分析,最终生成 **汇编代码**。
|
||||
|
||||
#### 操作命令
|
||||
|
||||
```bash
|
||||
# 生成汇编代码
|
||||
g++ -S main.i -o main.s
|
||||
```
|
||||
|
||||
#### 编译步骤详解
|
||||
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
│ Source Code (.i) │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ (1) Lexical │ ←│ Convert character stream into Token stream
|
||||
│ │ Analysis │ │ int main() -> [int] [main] [(] [)] [{] ...
|
||||
│ └────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ (2) Syntax │ ←│ Build AST from Token stream
|
||||
│ │ Analysis │ │ Check grammatical correctness
|
||||
│ └────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ (3) Semantic │ ←│ Type checking, scope analysis
|
||||
│ │ Analysis │ │ Ensure semantic legality
|
||||
│ └────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ (4) IR Generation │ ←│ Generate platform-independent IR
|
||||
│ │ │ │ e.g., Three-address code, SSA form
|
||||
│ └────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ (5) Code Optimizer │ ←│ Improve execution efficiency
|
||||
│ │ │ │ e.g., Constant folding, DCE
|
||||
│ └────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────────┐ │
|
||||
│ │ (6) Target Code │ ←│ Generate assembly code (.s)
|
||||
│ │ Generation │ │ Platform-specific instructions
|
||||
│ └────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ Assembly Code (.s) │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
#### 词法分析示例
|
||||
|
||||
**源代码片段**:
|
||||
|
||||
```c
|
||||
int result = a + b * 2;
|
||||
```
|
||||
|
||||
**词法分析输出 (Token 流)**:
|
||||
|
||||
| Token | 类型 | 词面值 |
|
||||
|:-----:|------|--------|
|
||||
| 1 | KEYWORD | `int` |
|
||||
| 2 | IDENTIFIER | `result` |
|
||||
| 3 | OPERATOR | `=` |
|
||||
| 4 | IDENTIFIER | `a` |
|
||||
| 5 | OPERATOR | `+` |
|
||||
| 6 | IDENTIFIER | `b` |
|
||||
| 7 | OPERATOR | `*` |
|
||||
| 8 | LITERAL | `2` |
|
||||
| 9 | PUNCTUATION | `;` |
|
||||
|
||||
> **NOTE**:词法分析器不关心语法是否正确,只负责将字符流切割为有意义的 Token。语法正确性由下一阶段的 **Parser** 判断。
|
||||
|
||||
#### 汇编代码示例
|
||||
|
||||
以下是一段 C 代码及其对应的 x86-64 汇编输出:
|
||||
|
||||
**C 源码**:
|
||||
|
||||
```c
|
||||
int add(int x, int y)
|
||||
{
|
||||
return x + y;
|
||||
}
|
||||
```
|
||||
|
||||
**生成的汇编代码 (`main.s` 截选)**:
|
||||
|
||||
```asm
|
||||
add:
|
||||
pushq %rbp # Save frame pointer
|
||||
movq %rsp, %rbp # Set up new stack frame
|
||||
movl %edi, -4(%rbp) # Store param x at [rbp-4]
|
||||
movl %esi, -8(%rbp) # Store param y at [rbp-8]
|
||||
movl -4(%rbp), %eax # Load x into eax
|
||||
movl -8(%rbp), %edx # Load y into edx
|
||||
addl %edx, %eax # eax = eax + edx (core addition)
|
||||
popq %rbp # Restore frame pointer
|
||||
ret # Return (result in eax)
|
||||
```
|
||||
|
||||
> **NOTE**:每一行汇编对应一条 **机器指令**,汇编器将人类可读的助记符(如 `movl`, `addl`)翻译为二进制机器码。
|
||||
---
|
||||
|
||||
### 3.3 汇编阶段 (Assembly)
|
||||
|
||||
> 汇编器将汇编代码翻译为 **机器码**,生成 **可重定位目标文件**。
|
||||
|
||||
#### 操作命令
|
||||
|
||||
```bash
|
||||
# 生成目标文件
|
||||
g++ -c main.s -o main.o
|
||||
```
|
||||
|
||||
#### 目标文件结构
|
||||
|
||||
目标文件 (`.o`) 采用 **PE/COFF**(Portable Executable / Common Object File Format)格式,主要包含以下节区:
|
||||
|
||||
| 节区 | 名称 | 存储内容 |
|
||||
|:----:|------|----------|
|
||||
| `.text` | 代码段 | 程序的机器指令 |
|
||||
| `.data` | 数据段 | 已初始化的全局变量和静态变量 |
|
||||
| `.bss` | BSS 段 | 未初始化的全局变量和静态变量(启动时清零)|
|
||||
| `.rodata` | 只读数据段 | 字面常量(如字符串字面量)|
|
||||
| `.symtab` | 符号表 | 函数和全局变量的符号信息 |
|
||||
| `.strtab` | 字符串表 | 符号名称字符串 |
|
||||
| `.rel.text` / `.rel.data` | 重定位表 | 需要重定位的信息 |
|
||||
---
|
||||
|
||||
### 3.4 链接阶段 (Linking)
|
||||
|
||||
> 链接器将多个目标文件及库文件合并,生成最终的 **可执行文件**。
|
||||
|
||||
#### 操作命令
|
||||
|
||||
```bash
|
||||
# 链接单个目标文件
|
||||
g++ main.o -o main.exe
|
||||
|
||||
# 链接多个目标文件
|
||||
g++ main.o utils.o -o app.exe
|
||||
```
|
||||
|
||||
#### 链接器核心任务
|
||||
|
||||
##### ① 符号解析 (Symbol Resolution)
|
||||
|
||||
将每个符号引用与其定义进行关联。未找到定义的符号称为 **未定义符号 (Undefined Symbol)**,将导致链接错误。
|
||||
|
||||
```
|
||||
main.o calls printf()
|
||||
|
|
||||
v
|
||||
Linker looks up printf in libstdc++.a / libmingw32.a (MinGW-w64 runtime)
|
||||
|
|
||||
v
|
||||
Found! -> Symbol resolved
|
||||
```
|
||||
|
||||
##### ② 重定位 (Relocation)
|
||||
|
||||
将目标文件中的相对地址转换为最终的可执行内存地址。
|
||||
|
||||
```
|
||||
重定位前 (目标文件中):
|
||||
call printf @ 偏移量 0x10
|
||||
|
||||
↓ 重定位
|
||||
|
||||
重定位后 (可执行文件中):
|
||||
call printf @ 绝对地址 0x7ffc12345678
|
||||
```
|
||||
|
||||
##### ③ 节区合并 (Section Merging)
|
||||
|
||||
将来自不同目标文件的同类节区合并为一个。
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
│ main.o .text ──┐ │
|
||||
│ ├→ Merge → Executable .text │
|
||||
│ utils.o .text ─┘ │
|
||||
│ │
|
||||
│ main.o .data ──┐ │
|
||||
│ ├→ Merge → Executable .data │
|
||||
│ utils.o .data ─┘ │
|
||||
└────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 完整链接示例
|
||||
|
||||
假设有以下多文件项目结构:
|
||||
|
||||
```
|
||||
project/
|
||||
|-- main.c # 主程序入口
|
||||
|-- utils.c # 工具函数
|
||||
+-- math.c # 数学函数
|
||||
```
|
||||
|
||||
**步骤演示**:
|
||||
|
||||
```bash
|
||||
# Step 1: 分别编译每个 .c 文件为 .o 目标文件(此时还未链接)
|
||||
g++ -c main.c -o main.o # main.o 中引用了 add() 和 printf(),但不知道它们在哪里
|
||||
g++ -c utils.c -o utils.o # utils.o 定义了 add()
|
||||
g++ -c math.c -o math.o # math.o 定义了 multiply()
|
||||
|
||||
# Step 2: 链接器将所有 .o 合并 + 关联 C 标准库
|
||||
# 此时链接器会发现:
|
||||
# - add() 在 utils.o 中定义 --> 符号解析成功
|
||||
# - multiply() 在 math.o 中定义 --> 符号解析成功
|
||||
# - printf() 在 msvcrt.dll (Windows 系统运行时) 中定义 --> 符号解析成功
|
||||
g++ main.o utils.o math.o -o app.exe
|
||||
|
||||
# Step 3: 运行最终的可执行文件
|
||||
app.exe
|
||||
```
|
||||
|
||||
**如果链接失败(符号未找到)**:
|
||||
|
||||
```bash
|
||||
g++ main.o -o app.exe
|
||||
undefined reference to 'add' # 链接错误:缺少 utils.o
|
||||
undefined reference to 'printf' # 链接错误:未链接运行时库
|
||||
collect2.exe: error: ld returned 1 exit status
|
||||
```
|
||||
|
||||
> **NOTE**:**编译错误 (Compile Error)** 和 **链接错误 (Link Error)** 是不同的:
|
||||
> - 编译错误:语法问题,如缺少分号、类型不匹配(编译阶段报错)
|
||||
> - 链接错误:符号缺失,如函数声明了但未定义、忘记链接库文件(链接阶段报错)
|
||||
---
|
||||
|
||||
## 四、完整编译流程速查
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A["源文件<br/>(.c/.h)"] --> B["预处理器<br/>Preprocessor"]
|
||||
B --> C[".i 文件"]
|
||||
C --> D["编译器<br/>Compiler"]
|
||||
D --> E[".s 文件<br/>(汇编代码)"]
|
||||
E --> F["汇编器<br/>Assembler"]
|
||||
F --> G[".o 文件<br/>(目标文件)"]
|
||||
G --> H["链接器<br/>Linker"]
|
||||
H --> I[".exe 文件<br/>(可执行文件)"]
|
||||
```
|
||||
|
||||
### 一键编译命令
|
||||
|
||||
```bash
|
||||
# 完整编译过程(一步完成)
|
||||
g++ main.c -o main.exe
|
||||
|
||||
# 分步编译(便于调试)
|
||||
g++ -E main.c -o main.i # 预处理
|
||||
g++ -S main.i -o main.s # 编译
|
||||
g++ -c main.s -o main.o # 汇编
|
||||
g++ main.o -o main.exe # 链接
|
||||
```
|
||||
---
|
||||
|
||||
## 本节小结
|
||||
|
||||
| 阶段 | 输入文件 | 输出文件 | 关键操作 |
|
||||
|:----:|----------|----------|----------|
|
||||
| 预处理 | `.c` | `.i` | 宏展开、头文件包含、去注释 |
|
||||
| 编译 | `.i` | `.s` | 词法/语法/语义分析、代码优化 |
|
||||
| 汇编 | `.s` | `.o` | 生成机器码和目标文件 |
|
||||
| 链接 | `.o` | `.exe` | 符号解析、重定位、合并 |
|
||||
---
|
||||
|
||||
> **上一节**:[← 0.1 编程哲学导论](./0.1%20Philosophy%20-%20Intro.md)
|
||||
> **下一节**:待续
|
||||
Reference in New Issue
Block a user