C语言学习:编译和链接

一、翻译环境 & 运行环境

在标准 C(ANSI C)中,程序处理分为两个独立环境:

  1. 翻译环境 :将.c源代码,转换为二进制的可执行机器指令(编译 + 链接阶段)。
  2. 运行环境执行可执行程序,输出运行结果。

整体流程:多个.c源文件 → 翻译环境 →可执行程序 → 运行环境 → 输出结果


二、翻译环境的完整流程

翻译环境 = 编译 + 链接 其中编译 又细分为:预处理(预编译)→ 编译 → 汇编

1. 编译阶段

  • 多个.c文件单独编译 ,每个文件生成目标文件
    • Windows 系统:后缀为**.obj**
    • Linux 系统:后缀为**.o**
  • 工具:编译器(Windows 常用cl.exe,Linux 常用gcc

2. 链接阶段

  • 参与文件:所有编译生成的目标文件 + 链接库(系统运行时库、第三方库)
  • 工具:链接器(Windows 常用link.exe
  • 最终产物:可执行程序 (Windows 为.exe,Linux 为无后缀可执行文件)

2.1 预处理(预编译)

核心作用

处理源文件 / 头文件的预编译指令 ,生成后缀为 .i 的纯 C 代码文件。

GCC 实操命令

复制代码
gcc -E test.c -o test.i

主要处理规则

  1. 宏处理删除#define,完全展开所有宏定义;
  2. 条件编译 :处理#if/#ifdef/#elif/#else/#endif
  3. 头文件嵌入 :递归展开#include,把头文件内容直接插入源码
  4. 清理注释:删除所有单行 / 多行注释;
  5. 调试标记:添加行号、文件名标识,用于后续调试;
  6. 保留指令保留#pragma编译器指令。

💡 排查技巧:宏定义、头文件包含异常时,可直接查看.i文件定位问题


2.2 编译

核心作用

对预处理后的.i文件 ,依次做词法分析→语法分析→语义分析→代码优化 ,生成汇编代码文件(.s

GCC 实操命令

复制代码
gcc -S test.i -o test.s

2.2.1 词法分析

  1. 原理:扫描器 将代码字符切割为记号(关键字、标识符、字面量、运算符等);
  2. 示例:代码 array[index] = (index+4 )*(2+6);经过词法分析后,会被拆分为16 个独立记号,完成字符到代码单元的转换。

后续编译流程

词法分析后还会执行

  • 语法分析构建语法树,检查语法格式是否合法;
  • 语义分析:检查类型匹配、作用域、表达式合法性;
  • 代码优化 :简化冗余计算(示例中2+6会直接优化为8)。

2.3 汇编

核心作用

编译生成的汇编代码(.s ,翻译为二进制机器指令 ,生成目标文件

  • 特点:逐条翻译、几乎不做优化,一条汇编语句对应一条机器指令。
  • 文件后缀:Linux 为 .o,Windows 为 .obj

GCC 实操命令

复制代码
gcc -c test.s -o test.o

2.4 链接

核心作用

多个目标文件 + 系统库文件 整合,解决多文件 / 多模块调用问题 ,最终生成可执行程序

三大核心步骤

  1. 地址和空间分配:统一规划所有代码、数据的内存布局
  2. 符号决议 :匹配函数、全局变量的声明与定义
  3. 重定位:修正跨模块调用的地址,补全真实内存地址

实例解析(test.c + add.c

  1. test.cextern 声明外部函数 Add、全局变量 g_val,但编译时不知道真实地址;
  2. add.c 定义 Add 函数和 g_val 全局变量;
  3. 两个文件分别编译生成 test.oadd.o
  4. 链接器找到 Addg_val 真实地址,修正 test.o 中的调用指令,该过程即重定位

底层拓展

  • 目标文件格式:Linux 下为 ELF 格式
  • 推荐书籍:《程序员的自我修养》(详解链接底层原理)

三、运行环境

1. 程序载入内存

  • 操作系统环境(Windows/Linux):由操作系统自动完成,操作系统从磁盘读取可执行文件,分配内存空间,将程序加载到内存。
  • 裸机 / 独立环境(单片机、嵌入式) :无操作系统,需手动载入,或直接将编译后的可执行代码烧录到只读存储器(ROM/Flash)中。

2. 启动执行,调用main函数

程序加载完成后,系统 / 硬件初始化完成,自动调用main()函数main是 C 程序的唯一入口。

3. 代码执行,内存使用

程序运行时主要用到两类内存:

  1. 运行时栈(Stack)
    • 用于存储局部变量、函数调用返回地址
    • 函数调用时自动分配,函数结束后自动释放,生命周期随函数。
  2. 静态内存(static)
    • 存储static修饰的静态变量、全局变量
    • 程序整个运行期间都保留值,不会随函数结束释放。

补充:还有堆(Heap) ,用于malloc/free动态内存分配。

4. 程序终止

  • 正常终止main函数执行完毕返回(return 0)。
  • 意外终止:程序崩溃、强制关闭、异常报错等。
相关推荐
chimchim662 小时前
Azure ADF(Azure Data Factory 数据工厂)学习
学习·microsoft·azure
小新同学^O^2 小时前
简单学习 --> Transformer架构
学习·架构·transformer
他们叫我阿冠2 小时前
Docker的基础学习
学习·docker·容器
辰海Coding11 小时前
MiniSpring框架学习笔记-解决循环依赖的简化IoC容器
笔记·学习
晓梦林11 小时前
cp520靶场学习笔记
android·笔记·学习
心中有国也有家13 小时前
cann-recipes-infer:昇腾 NPU 推理的“菜谱集合”
经验分享·笔记·学习·算法
Upsy-Daisy13 小时前
AI Agent 项目学习笔记(八):Tool Calling 工具调用机制总览
人工智能·笔记·学习
LuminousCPP14 小时前
数据结构 - 线性表第四篇:C 语言通讯录优化升级全记录(踩坑 + 思考)
c语言·开发语言·数据结构·经验分享·笔记·学习
魔法阵维护师14 小时前
从零开发游戏需要学习的c#模块,第十四章(保存和加载)
学习·游戏·c#