一.预编译阶段预处理gcc -E main.cpp main.i输入.cpp源码输出.i预处理后的文本文件仍然是C/C源码文本不是机器码。1.#define宏展开删除#define定义例子#define PI 3.14代码里所有PI全部替换成3.14宏请按不原地展开之后删掉#define这一行。注意只是纯文本替换不做语法检查写错宏预处理阶段不会报错等到后面编译阶段才报错。2.处理条件预编译 #if / #ifdef / #ifndef / #endif根据条件判断直接删掉不满足条件的代码行。例#ifdef WIN32 如果不是Windows平台这一段代码直接被删掉不会参与后续编译。3.处理#include头文件把#include xxx.h对应的整个头文件内容原封不动复制粘贴到当前代码这个位置。头文件会递归处理头文件里面还包含别的头文件继续复制。4.删除所有注释//单行注释和/*块注释*/全部删掉替换成空格。注释对程序没有任何作用预处理直接清理掉。5.添加行号文件名标识就是# 行号 “文件名”标记。后面编译报错gdb调试的时候才能告诉你main.cpp第25行出错。如果没有这个标记编译器分不清代码来自哪个文件哪一行。6.保留#pragma指令#pragma是留给特定编译器的指令不是标准C语法。预处理不会删掉交给后面编译器处理。例#pragma once防止头文件重复包含。预处理总结纯文本操作不做语法检查只是增删替换。二.编译阶段gcc -S main.i -o main.s输入预处理后的.i文件输出.s汇编代码文件文本一共5步词法分析-语法分析-语义分析-代码优化-汇总符号1.词法分析把连续代码切成一个个[单词token]把代码字符串切割关键字if变量名a运算符数字10。比如int a 12拆成int a 1 2 识别非法字符比如中文标点这里就报错。2.语法分析按照C语法生成语法树抽象语法树AST检查句子语法是否合法。比如int 10; 变量名确实语法错误直接报编译错误。3.语义分析检查类型是否合法重点语法没问题但类型不对在这里报错。例如int a abc; 把字符串赋值给int语法没问题语义错误。做类型检查类型转换。4.代码优化对AST做优化简化代码。比如int a 12; 直接优化成a3不用运行时再计算。5.汇总符号收集所有符号建立符号表。符号变量名函数名。记录函数变量在那定义类型是什么。编译阶段产出汇编代码.s是人类刻度的汇编指令文本。三.汇编阶段gcc -c main.s -o main.o输入.s汇编代码输出目标文件.oWindows叫.obj二进制文件。汇编器as把汇编指令翻译成CPU能识别的二进制机器码。1.将汇编指令翻译成二进制划分各个section段.test段存放代码指令函数二进制机器码.data段已经初始化的全局变量.bss段未初始化全局变量不占用目标文件空间只记录大小。2.生成符号表记录本.o里面的函数变量符号。***重点此时只是单个目标文件符号没有分配最终虚拟地址如果调用别的文件的函数汇编阶段只标记[这个符号待解析]不知道它的真实地址。举例main.o调用func()func在另一个test.o里面。汇编阶段不知道func在哪先记下来留给链接阶段处理。四.链接阶段linkgcc把多个.o合并成可执行文件输入多个.o目标文件静态库.a输出可执行文件a.out/main.exe两大核心步骤1.合并段合并符号表符号解析分配虚拟地址1把所有.o里面同名section合并所有.text合并成一个大代码段所有.data合并成数据段。2合并所有目标文件的符号表。3符号解析找到每一个符号唯一的定义。比如main.o引用func链接器去其他.o里面找func的定义找到func对应的符号。4给每一个符号分配最终的虚拟地址。2.符号重定位最核心重定位把代码里所有引用符号的地方把原来临时占位的地址替换成刚刚分配好的虚拟地址。举个例子main函数调用func()在main.o里面call指令的地址是占位符。链接找到func的虚拟地址修改call指令填入func真正的虚拟地址。如果链接找不到符号定义报未定义引用undefined reference这是链接期错误不是编译错误链接的两种类型1.静态链接多个目标文件.o打包归档在一起的文件不是可执行程序。链接阶段行为链接器扫描你的.o目标文件发现你调用了库里的函数直接把被调用函数对应的那一段机器码从静态库里面拷贝出来合并到最终的可执行文件中。特点1运行时不再依赖原静态库文件库代码已经完整复制exe/a.out拿到这个可执行文件就能直接跑移植方便。2可执行文件体积更大因为包含了库的机器码。3缺点多个程序如果都用同一个静态库每个程序都会单独复制一份库代码磁盘上存在多份重复代码浪费磁盘空间。4库升级如果静态库升级必须重新编译链接整个项目重新生成可执行程序否则程序依旧使用旧版本库代码。总结静态链接在编译链接期把所需要的库代码复制进可执行文件运行不依赖库程序体积大多程序会重复保存库代码。2.动态链接动态库后缀Linux .soWindows .dll链接阶段行为不会把库的机器码复制进可执行文件。链接器只做两件事1.检查你调用的函数在动态库里面是否存在完成符号校验2.在可执行文件里仅仅记录依赖哪个动态库用到哪些函数只留下一张“函数索引表”。程序运行时行为操作系统加载可执行文件到内存之后再根据记录的依赖信息去磁盘找到对应的.so/.dll动态库把动态库加载进内存然后完成符号重定位把函数调用绑定到动态库内函数的真实地址。特点1磁盘上只保存一份动态库多个应用程序可以共享内存里这一份动态库代码节省磁盘和物理内存。2可执行文件本身体积更小。3库升级替换新版本的.so/.dll文件即可不需要重新编译链接你的程序下次运行程序自动加载新版本库。4缺点运行环境必须存在对应的动态库文件缺少库直接运行失败经典报错xxx.so not found移植时需要连带部署动态库。总结动态链接编译时只记录库依赖不复制代码程序运行时操作系统才加载动态库多程序共享同一份库程序体积小但运行环境必须有对应动态库。