Files
ProgramPhilosophy/Doc/0. Philosophy - Intro/0.2 Philosophy - Basic.md
T

465 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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)
> **下一节**:待续