C语言编译过程可以分为以下几个主要步骤:1. **预处理(Preprocessing)**: - 预处理器读取源代码文件,处理其中的预处理指令,如`#include`、`#define`、`#ifdef`等。 - 预处理器将预处理后的代码输出到一个新的文件

摘要:bin 文件通常用于嵌入式裸机程序的烧录,elf 可执行文件通常运行在操作系统之上。 bin 是扁平的二进制文件,没有任何说明,它假设加载它的环境(如嵌入式引导程序,BootRom)已经预先知道了代码存放的地址,代码的入口,数据段,代码段的
bin 文件通常用于嵌入式裸机程序的烧录,elf 可执行文件通常运行在操作系统之上。 bin 是扁平的二进制文件,没有任何说明,它假设加载它的环境(如嵌入式引导程序,BootRom)已经预先知道了代码存放的地址,代码的入口,数据段,代码段的地址。大家如果烧录过嵌入式裸机程序应该有所体会。 elf 则是带有详细说明和装配图的文件,因此 elf 可执行程序的运行是需要对其所包含的信息进行解析并建立执行环境的,这就决定了其不可能作为裸机程序去执行。 一、C语言执行需要的内存环境 一个可执行文件加载过程中,需要创建执行所需要的内存空间。对于操作系统而言,一般指的是每个进程的虚拟地址空间。一个进程的内存空间一般存在四个核心区域,代码段(.text),数据段(.data),堆(.heap),栈(.stack)。 代码段,用于存放编译后的机器指令。 数据段: data 段,已初始化的非零全局变量和静态变量。 bss 段,未初始化或者初始化值为0的全局变量,静态变量。 rodata段,const 关键字修饰的常量,只读。 堆,用于动态空间分配,C语言中需要手动分配和释放(malloc/free)。 栈,存储局部变量、函数参数、返回地址,自动分配和释放,遵循 “先进后出” 规则。 需要注意的是,栈的地址是从高地址到低地址,堆的地址是从低地址到高地址。 二、C语言编译过程 C语言从源代码到可执行文件一般需要四个过程,即预处理,编译,汇编,链接。 预处理,处理源代码中的预处理指令(以#开头),生成纯 C 代码(.i文件)。 宏替换:展开#define定义的宏(如#define PI 3.14替换为实际值)。 文件包含:将#include指令指向的头文件(如stdio.h)内容插入当前文件。 条件编译:根据#if、#ifdef等指令保留或删除部分代码(如调试代码#ifdef DEBUG ... #endif)。 删除注释:移除//和/* */注释,不影响代码逻辑。 编译,将预处理后的纯 C 代码(.i文件)转换为汇编代码(.s文件)。 语法分析:检查代码语法是否符合 C 语言规则(如括号匹配、关键字使用等),若出错则终止编译。 语义分析:验证代码逻辑合理性(如变量未声明就使用、类型不匹配等)。 中间代码生成:将合法代码转换为中间表示(如三地址码)。 优化:对中间代码进行优化(如常量折叠、循环展开),提升执行效率。 汇编生成:将优化后的中间代码转换为对应 CPU 架构的汇编指令。 汇编,将汇编代码(.s文件)转换为机器指令(二进制目标文件,.o或.obj)。 把汇编指令一一对应为 CPU 可识别的二进制 opcode(如call printf转换为对应的机器码),生成的目标文件包含: 二进制指令(代码段) 变量数据(数据段) 符号表(记录函数、变量的地址信息,供后续链接使用)。 目标文件是 “部分编译” 的结果,可能包含未解析的外部符号(如printf函数的地址尚未确定)。 链接,将多个目标文件(.o)和库文件(如libc.so)合并,生成可执行文件。 符号解析:找到所有外部符号的实际地址(如printf在 C 标准库中的地址)。 重定位:调整目标文件中指令的地址(因为多个文件合并后,原地址可能偏移)。 合并段:将多个目标文件的代码段、数据段等合并为统一的内存布局。 链接又分为静态链接和动态链接。Linux 环境下使用 gcc 进行编译,默认使用动态链接。 静态链接将程序依赖的库函数代码(如printf、malloc)直接复制到可执行文件中,形成一个独立的二进制文件。 依赖的库称为 “静态库”(Windows 下为.lib,Linux 下为.a)。 动态链接仅在可执行文件中记录库函数的引用信息(如函数名、库路径),不复制库代码。程序运行时,由操作系统的动态链接器(如 Linux 的ld.so,Windows 的ntdll.dll) 加载依赖的库文件到内存,并解析引用,后面会展开讲。 依赖的库称为 “动态库”(Windows 下为.dll,Linux 下为.so,macOS 下为.dylib)。 Linux默认的动态库链接是/lib,/usr/lib。
阅读全文