C语言:编译和链接

1. 翻译环境和运行环境

  1. 翻译环境:预编译+编译+汇编+链接
1. 翻译环境和运行环境

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

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

2.运行环境 :执行可执行程序,输出运行结果。

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

2.翻译环境

翻译环境是由编译和链接 两个大的过程组成的,而编译 又可以分解成:预处理(有些书也叫预编
译)、编译、汇编三个过程。

1编译阶段

多个.c文件单独编译,每个文件生成目标文件

Windows 系统 :后缀为.obj

Linux 系统 :后缀为.o

**工具:**编译器(Windows 常用cl.exe,Linux 常用gcc)

2链接阶段

参与文件: 所有编译生成的目标文件 + 链接库(系统运行时库、第三方库)

工具: 链接器(Windows 常用link.exe)

最终产物:可执行程序(Windows 为.exe,Linux 为无后缀可执行文件)

2.1预处理(预编译****)

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

GCC 实操命令

cpp 复制代码
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 实操命令

cpp 复制代码
gcc -S test.i -o test.s
2.2.1 词法分析

原理: 扫描器将代码字符切割为记号(关键字、标识符、字面量、运算符等);

**示例:**代码 arrayindex = (index+4)*(2+6);经过词法分析后,会被拆分为16 个独立记号,完成字符到代码单元的转换。


后续编译流程

词法分析后还会执行:

语法分析 :构建语法树,检查语法格式是否合法;

语义分析 :检查类型匹配、作用域、表达式合法性;

代码优化:简化冗余计算(示例中2+6会直接优化为8)。

2.3汇编

核心作用是将编译生成的汇编代码(.s),翻译为二进制机器指令,生成目标文件。

特点 :逐条翻译、几乎不做优化,一条汇编语句对应一条机器指令。

文件后缀:Linux 为 .o,Windows 为 .obj

GCC 实操命令

cpp 复制代码
gcc -c test.s -o test.o
2.4链接

核心作用是将多个目标文件 + 系统库文件整合,解决多文件 / 多模块调用问题,最终生成可执行程序。
三大核心步骤

1.地址和空间分配: 统一规划所有代码、数据的内存布局

2.符号决议: 匹配函数、全局变量的声明与定义

**3.重定位:**修正跨模块调用的地址,补全真实内存地址

2.4.1实例解析(test.c + add.c)
cpp 复制代码
#include <stdio.h>
//test.c
//声明外部函数
extern int Add(int x, int y);
//声明外部的全局变量
extern int g_val;
int main()
{
    int a = 10;
    int b = 20;
    int sum = Add(a, b);
    printf("%d\n", sum);
    return 0;
}


add.c
int g_val = 2022;
int Add(int x, int y)
{
return x+y;
}

test.c 用 extern 声明外部函数 Add、全局变量 g_val,但编译时不知道真实地址;

add.c 定义 Add 函数和 g_val 全局变量;

两个文件分别编译生成 test.o、add.o;

链接器找到 Add、g_val 真实地址,修正 test.o 中的调用指令,该过程即重定位。

2.5运行环境

一.程序载入内存

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

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

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

三.代码执行,内存使用

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

1.运行时栈(Stack)

用于存储局部变量、函数调用返回地址

函数调用时自动分配,函数结束后自动释放,生命周期随函数。

2.静态内存(static)

存储static修饰的静态变量、全局变量

程序整个运行期间都保留值,不会随函数结束释放

2.6程序终止

1.正常终止: main函数执行完毕返回(return 0)。

**2.意外终止:**程序崩溃、强制关闭、异常报错等。

相关推荐
hsjiasb6 小时前
FreeRTOS学习(二十七)——任务栈与栈溢出检测
开发语言·stm32·学习·学习笔记·freertos
丰锋ff6 小时前
3.1 Qt事件之事件处理器
开发语言·qt
多弗朗皮卡丘7 小时前
C语言梦开始的地方7:函数
c语言·开发语言
月华路7 小时前
G1 垃圾回收:脏卡队列、记忆集与并发精化线程机制
java·开发语言
gugucoding8 小时前
47. 【Java】Java内存模型(JMM)与可见性
java·开发语言
m0_380743878 小时前
从零调用 Claude 教程
开发语言·python·node.js
起床学FPGA8 小时前
AI回答问题之后,会自动跳到最下面的问题
开发语言·javascript·ecmascript
x_x-F9 小时前
网络数据从网卡到用户态:一场基于内存所有权转移的接力赛
开发语言·网络·php
名字还没想好☜9 小时前
Java 线上内存泄漏排查实战:jmap 导堆、MAT 找 GC Roots 与四类常见泄漏
java·开发语言·jvm·内存泄漏
码匠许师傅9 小时前
【C++ 面试真题】聊聊 C++ 的多继承与虚继承
开发语言·c++·面试