参考课程

参考书目:

gcc/g++编译命令

基本编译流程:预处理 → 编译 → 汇编 → 链接

  1. 预处理:处理宏、头文件、条件编译等
    • 展开所有宏定义
    • 处理所有#include指令
    • 处理条件编译指令(#ifdef, #ifndef, #if, #else, #endif)
    • 删除所有注释
    • 添加行号和文件名标识(可使用-P去除)
    • 保留#pragma指令
  2. 编译:将预处理后的代码转换为汇编语言
    • 词法分析和语法分析
    • 语义分析
    • 中间代码生成
    • 代码优化
    • 生成目标平台的汇编代码
  3. 汇编:将汇编代码转换为机器码(目标文件)
    • 将汇编指令翻译为机器指令
    • 生成可重定位的目标文件(.o文件)
    • 生成符号表
    • 处理标签和地址引用
  4. 链接:将多个目标文件库文件连接成可执行文件
    • 符号解析(解决未定义的引用)
    • 重定位(调整地址引用)
    • 合并代码和数据段
    • 链接启动代码和库文件
    • 生成最终的可执行文件格式

命令选项

选项 描述 示例
-o <file> 指定输出文件名 gcc -o program main.c
-E 只预处理,不编译 gcc -E main.c -o main.i
-S 生成汇编代码 gcc -S main.c -o main.s
-c 编译生成机器码不链接 gcc -c main.c -o main.o
-save-temps 保存所有中间文件 gcc -save-temps main.c -o program
-pipe 使用管道代替临时文件 gcc -pipe main.c -o program

优化级别选项:

选项 描述 效果
-O0 不优化(默认) 编译快,调试方便
-O1 基本优化 减少代码大小和执行时间
-O2 更多优化(推荐) 包括所有不涉及空间换时间的优化
-O3 激进优化 包括向量化等高级优化
-Os 优化代码大小 优先减小可执行文件大小
-Ofast 快速优化 可能违反严格标准
-Og 优化调试体验 在保持可调试性的同时优化

如果使用了优化选项:

  • 编译的时间将更长;
  • 目标程序不可调试;
  • 有效果,但是不可能显著提升程序的性能。(关键还是要看源代码的实现)

目标文件 vs 可执行文件

特性 目标文件 (.o/.obj) 可执行文件
文件扩展名 .o (Unix/Linux) .obj (Windows) 无扩展名 (Linux) .exe (Windows)
文件类型 可重定位目标文件 可执行目标文件
链接状态 未链接 已完全链接
能否直接运行 ❌ 不能直接执行 ✅ 可以直接运行
地址引用 相对地址/未解析符号 绝对地址/已解析符号
包含内容 单个模块的代码和数据 完整程序的所有代码和数据
段结构 需要重定位的段 加载到内存的最终段
依赖关系 依赖其他目标文件/库 可能依赖动态库,但独立运行

示例:将hello.c编译为可执行文件hello

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 第1步:预处理
gcc -E hello.c -o hello.i

# 第2步:编译为汇编
gcc -S hello.i -o hello.s

# 第3步:汇编为目标文件
gcc -c hello.s -o hello.o

# 第4步:链接为可执行文件
gcc hello.o -o hello
# 运行程序
./hello

# 一步完成所有步骤(对比)
gcc hello.c -o hello_one_step

静态库、动态库

一般来讲,通用的函数和类不提供源代码文件,而是编译成二进制文件。

库的二进制文件

  • 静态库
  • 动态库

如果动态库和静态库同时存在,编译器将优先使用动态库。

静态库

  • 文件扩展名: .a (Unix/Linux), .lib (Windows)
  • 本质: 多个目标文件(.o)的归档文件
  • 链接时机: 编译/链接时
  • 特点:
    • 程序在编译时会把库文件的二进制代码链接到目标程序中
    • 库代码被复制到最终的可执行文件中
    • 如果多个程序中用到了同一静态库中的函数或类,就会存在多份拷贝
    • 静态库的链接是在编译时期完成的,执行的时候代码加载速度快。
    • 生成的目标程序的可执行文件比较大,浪费空间。
    • 程序的更新和发布不方便,如果某一个静态库更新了,所有使用它的程序都需要重新编译

创建方式

1
g++ -c -o lib库名.a 源代码文件清单
1
2
g++ -c -o libpublic.a  public.cpp
#会生成libpublic.a 是一个二进制文件

使用方式

1
g++ 选项 源代码文件名清单 -l库名 -L库文件所在的目录名
1
g++ -o demo01 demo01.cpp -lpublic -L/home/wucz/tools

动态库

  • 文件扩展名: .so (Unix/Linux), .dll (Windows), .dylib (macOS)
  • 本质: 独立的可执行代码模块
  • 链接时机: 运行时
  • 特点:
    • 可执行文件只包含引用,运行时动态加载
    • 如果多个进程中用到了同一动态库中的函数或类,那么在内存中只有一份,避免了空间浪费问题
    • 程序在运行的过程中,需要用到动态库的时候才把动态库的二进制代码载入内存。
    • 可以实现进程之间的代码共享,因此动态库也称为共享库。
    • 程序升级比较简单,不需要重新编译程序,只需要更新(重新制作)动态库就行了。

创建方式

1
g++ -fPIC -shared -o lib库名.so 源代码文件清单
1
2
g++ -fPIC -shared -o libpublic.so public.cpp
gcc -fPIC -shared -o libmath.so mathlib.c
选项 全称 含义 作用阶段
-fPIC Position Independent Code 生成位置无关代码 编译阶段
-shared Shared Library 生成共享库(动态库) 链接阶段

使用方式

运行可执行程序的时候,需要提前设置LD_LIBRARY_PATH环境变量,即指定动态库的目录

1
g++ 选项 源代码文件名清单 -l库名 -L库文件所在的目录名
1
g++ -o demo01  demo01.cpp -lpublic -L/home/wucz/tools

makefile