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

14 KiB
Raw Blame History

title, subtitle, version, category, order, tags, prev, next
title subtitle version category order tags prev next
编程基础 Programming Philosophy - Basic Concepts 1.0 哲学篇 · 基础 0.2
编程
哲学
基础
编译
规范
./0.1%20Philosophy%20-%20Intro.md null

0.2 编程基础

本节导读:掌握编程开发的规范体系,深入理解编译器的工作原理与完整编译流程。


一、开发规范体系

规范是团队协作的基石,良好的编码习惯是程序员的基本素养。

1.1 项目规范

  • 目录结构规范
  • 配置管理规范
  • 版本控制规范

示例 — 推荐的 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 类型 / 结构体名称
// 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 不良代码对比

// --- 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)

预处理器的任务是对源文件进行文本层面的替换和处理,生成 预处理后的源代码

操作命令

# 查看预处理结果
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)

#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 — 部分截取)

// #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)

编译器将预处理后的代码进行词法、语法、语义分析,最终生成 汇编代码

操作命令

# 生成汇编代码
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)     │
└──────────────────────────┘

词法分析示例

源代码片段

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 源码

int add(int x, int y)
{
    return x + y;
}

生成的汇编代码 (main.s 截选)

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)

汇编器将汇编代码翻译为 机器码,生成 可重定位目标文件

操作命令

# 生成目标文件
g++ -c main.s -o main.o

目标文件结构

目标文件 (.o) 采用 PE/COFFPortable Executable / Common Object File Format)格式,主要包含以下节区:

节区 名称 存储内容
.text 代码段 程序的机器指令
.data 数据段 已初始化的全局变量和静态变量
.bss BSS 段 未初始化的全局变量和静态变量(启动时清零)
.rodata 只读数据段 字面常量(如字符串字面量)
.symtab 符号表 函数和全局变量的符号信息
.strtab 字符串表 符号名称字符串
.rel.text / .rel.data 重定位表 需要重定位的信息

3.4 链接阶段 (Linking)

链接器将多个目标文件及库文件合并,生成最终的 可执行文件

操作命令

# 链接单个目标文件
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          # 数学函数

步骤演示

# 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

如果链接失败(符号未找到)

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) 是不同的:

  • 编译错误:语法问题,如缺少分号、类型不匹配(编译阶段报错)
  • 链接错误:符号缺失,如函数声明了但未定义、忘记链接库文件(链接阶段报错)

四、完整编译流程速查

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/>(可执行文件)"]

一键编译命令

# 完整编译过程(一步完成)
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 编程哲学导论 下一节:待续