一、 Vim 编辑器速览
Vim是一款强大的文本编辑器,掌握其快捷键能显著提升开发效率。
1.1 批量化注释
- 操作方法:
- 使用
Ctrl + v进入列选择模式。 - 选中需要注释的行。
- 按下
Shift + i。 - 输入注释符号(例如
//)。 - 按下
Esc键两次,即可完成批量注释。
- 使用
1.2 批量化去注释
- 操作方法:
- 使用
Ctrl + v进入列选择模式。 - 选中需要去除注释的列(即注释符号)。
- 直接按下
Delete键,即可批量删除注释。
- 使用
二、 GCC/G++ 编译器基础
GCC (GNU Compiler Collection) 和 G++ (GCC C++ Compiler) 是C/C++开发中不可或缺的编译工具。
2.1 基本使用与选项
- 输出可执行文件:
gcc -o <output_filename> <input_filename>,默认输出文件名为a.out。 - 指定C语言标准: 使用
-std=c99等选项指定C语言标准。 - C++ 文件后缀: C++ 源文件不仅可以使用
.cpp,还可以使用.cc或.cxx作为后缀名。需要注意的是,后缀名在编译过程中并非无关紧要,它会影响编译器对文件类型的判断。
2.2 权限与用户建议
- 通常不建议使用
root用户进行日常开发操作,以避免潜在的权限问题和安全风险。
三、 C/C++ 程序的编译流程
C/C++ 程序的编译是一个多阶段的过程,从源代码到可执行文件,每个阶段都有其特定的任务。编译过程的检查点也在此体现。
3.1 预处理 (Preprocessing)
- 功能: 展开头文件、宏定义替换、条件编译等。
- 头文件展开: 预处理器会将
#include指令指定的头文件内容拷贝到源代码中。安装GCC/G++时,也会下载相应的头文件。

-
条件编译: 通过
#define、#ifdef、#ifndef和#endif等宏定义,可以根据不同的条件裁剪代码,实现定制化功能。- 应用场景:
- 维护一份代码库,根据宏定义(如
V1或V2)动态开启或关闭特定功能,实现社区版、免费版、专业版等不同版本的功能裁剪。 - 例如,若
V1和V2均未定义,则ifdef V1和ifdef V2下的代码块会被裁掉,只保留else部分。
- 维护一份代码库,根据宏定义(如
- 命令行宏定义: 使用
gcc -D <MACRO_NAME>[=<VALUE>]可以在编译时动态添加宏定义,例如gcc -D V1=1 test.c,从而避免修改源代码。

- 应用场景:
-
产物: 经过预处理的
.i文件。- 示例:
gcc -E test.c -o test.i
- 示例:
3.2 编译 (Compilation)
- 功能: 将预处理后的源代码翻译成汇编语言。
- 产物: 汇编语言
.s文件。- 示例:
gcc -S test.i -o test.s
- 示例:
3.3 汇编 (Assembly)
- 功能: 将汇编语言翻译成机器码,生成二进制目标文件 (
.o文件)。 - 产物: 未链接的二进制目标
.o文件。- 示例:
gcc -c test.s -o test.o - 说明: 此时生成的
.o文件无法直接执行,因为它还没有解决外部符号引用(如库函数)的问题。一个文件是否可执行,不仅取决于其权限,还需要文件本身是一个完整的可执行文件格式。
- 示例:
3.4 链接 (Linking)
- 功能: 将多个目标文件(
.o文件)与所需的库(静态库或动态库)组合,生成最终的可执行文件。 - 产物: 可执行文件(如
a.out或通过-o指定的文件)。- 示例:
gcc test.o -o my.exe - 链接过程解决了代码中调用的,但并非由用户自己编写的公共库函数问题。
- 示例:
ESc是指令 -E -S -c的集合 然后iso是gcc之后生成的文件后缀.i .s .o这三个的集合 这样比较好记
3.4.1 库文件的概念
- 库:操作系统中预编译好的通用代码集合,包含大量常用的函数和数据。
- C标准库:例如,Linux下的
libc.so.6是一个高度鲁棒且效率极高的C标准库。 - 头文件:提供库中函数和数据的声明,作为库的“说明书”,使得编译器知道如何调用这些函数。安装开发环境通常意味着安装了C标准库及其对应的头文件。
- 命名约定: Linux动态库通常以
lib开头,.so结尾(如libc.so.6);Windows动态库以.dll结尾。Linux静态库以.a结尾;Windows静态库以.lib结尾。
3.4.2 静态链接与动态链接
- 动态链接 (Dynamic Linking):
- 机制: 在程序运行时根据需要加载共享库(Linux:
.so,Windows:.dll),而不是在编译时将库代码完全复制到可执行文件中。 - 优点:
- 节省磁盘空间和内存: 多个程序可以共享同一个动态库,内存中只需加载一份。
- 便于库的更新和维护: 更新库无需重新编译所有依赖程序。
- 缺点:
- 依赖共享库的存在: 一旦动态库缺失或版本不兼容,程序可能无法执行。
- 安全性: 共享库可能成为攻击目标。
- 查看依赖: 使用
ldd <executable>命令可以查看可执行程序所依赖的动态库。 - 默认行为:
gcc默认使用动态链接。

- 机制: 在程序运行时根据需要加载共享库(Linux:
我去网吧玩电脑,就相当于是去动态库里面直接找
- 静态链接 (Static Linking):
- 机制: 在编译时将所有必要的库代码完整地复制到可执行文件中。
- 优点:
- 生成独立的可执行文件: 不依赖外部库,易于分发和移植(具有很强的跨平台性)。
- 避免运行时库冲突。
- 缺点:
- 浪费磁盘空间和内存: 每个静态链接的程序都包含一份库代码,导致可执行文件体积增大。
- 库更新麻烦: 库更新需要重新编译整个程序。
- 强制静态链接: 使用
gcc -o <output_filename> <input_filename> -static。

在编译的时候,把库中的方法,拷贝到我自己的可执行程序中,就叫做静态链接(把网吧电脑卖给自己)

默认情况下,Linux上静态库没有被安装
静态链接的应用场景:
想要自己的代码有很强的跨平台性,不依赖于动态库,就可以这样
一般公司内部的软件都会这样子做
一般下软件其实也会把很多的动态库下下来
四、 Make/Makefile 自动化构建
Make 和 Makefile 是用于自动化编译和构建项目的工具。
4.1 Makefile 结构
- 依赖关系:
<目标文件> : <依赖文件列表> - 依赖方法:
<tab> <执行命令>(注意:命令前必须是tab键) - 示例:
mytest: test.c gcc -o test test.c

4.2 执行机制
- 扫描顺序: Makefile 从上到下扫描,默认构建第一个定义的目标。
- 效率优化: 除非目标文件比其依赖文件旧(即依赖文件更新),否则
make命令默认不会重新执行构建,以节省资源。make通过比较文件修改时间来判断是否需要重新构建。 - 强制执行:
- 使用
.PHONY: <目标>可以将目标标记为“伪目标”。伪目标总会被执行,即使存在同名的文件。 - 应用场景:
clean等操作通常被定义为伪目标,以确保每次执行时都能清除旧文件。
- 使用
4.3 依赖关系与方法的理解
- 核心: 依赖关系定义了生成某个目标所需的文件,而依赖方法则是生成该目标的具体步骤。
- 类比: “我是你儿子(依赖关系),我找你要钱(依赖方法)”。这形象地说明了只有存在依赖关系才能执行相应的方法。
五、 编译器的自举 (Compiler Bootstrapping)
编译器的自举是指使用编译器自身所支持的高级语言来重新实现和编译该编译器的过程,实现“自己编译自己”。
5.1 核心概念
- 自举: 编译器能够编译自己的源代码,用自己实现自己。这是一个“递归”和“自引用”的重要思想。
5.2 典型的自举过程
- 初始阶段: 使用其他语言(如汇编语言、C语言)实现一个简单的编译器版本(V0版本编译器)。
- 重写阶段: 用目标语言(即 V0 编译器将要支持的语言)重新编写编译器源代码(V1版本源代码)。
- 自举阶段: 使用 V0 编译器编译 V1 源代码,生成第一个用目标语言编写的编译器可执行文件(V1版本编译器)。
- 完全自举: 使用 V1 编译器重新编译 V1 源代码,验证其正确性和稳定性,生成一个完全由自身编译的、功能完备的编译器。
5.3 自举方法
-
T型图表示法:
源语言 → 目标语言 ↓ 实现语言例如,一个用C语言实现、能将C编译到x86汇编的编译器可以表示为
C → x86(实现语言:C)。 -
三阶段自举:
- 阶段1: S语言编译器用H语言实现。
- 阶段2: S语言编译器用S语言重写。
- 阶段3: 用阶段1的编译器编译阶段2的代码。
5.4 实际案例
- Go 语言的自举历程:
- 2007-2012年: Go 编译器最初用 C 语言实现。
- 2014年: 开始用 Go 语言重写编译器。
- 2015年: 实现完全自举,Go 编译器完全由 Go 编写。
- 现代Go编译器构建过程:
go build cmd/compile(Go编译器编译自己),go build cmd/link(Go链接器编译自己)。
- Rust 语言的自举历程:
- 早期: 使用 OCaml 实现
rustboot。 - 后期: 用 Rust 语言重写,实现自举。
- 现在:
rustc编译器完全由 Rust 编写。
- 早期: 使用 OCaml 实现
5.5 自举的优势
- 语言特性充分利用: 编译器开发者能深度利用自己的语言,并发现和改进语言设计。
- 性能优化: 更深入了解语言特性,针对性能瓶颈进行优化。
- 维护便利: 统一技术栈,减少对外部工具的依赖。
- 质量保证: 编译器本身成为语言的重要测试用例。
5.6 挑战与问题
- 循环依赖: 需要编译器来编译编译器,初始版本的获取是关键。
- 向后兼容: 语言演进时,如何维护旧版本编译器的兼容性。
- 可信计算基: 存在 Ken Thompson 的“信任的信任”问题,即编译器中的潜在恶意代码难以被发现。

2306

被折叠的 条评论
为什么被折叠?



