从源码到可执行文件的过程
g++ hello.cpp -o hello
1.预处理
g++ -E hello.cpp -o hello.i
//生成.i文件
//-o 指的是将结果输出并指定输出的文件名
- 处理#define 宏定义
- 处理所有的条件预编译指令,“#if #endif”
- 处理“#include”指令
- 删除所有的注释
- 添加行号和文件标识
2.编译
gcc -S hello.cpp(.i) -o hello.s
//生成.s文件(汇编文件)
- 词法分析
- 语法分析
- 语义分析
- 优化
3.汇编(将汇编代码转变为机器可以执行的指令)
gcc -c hello.cpp -o hello.o
4.链接
gcc hello.o -o hello
//hello 就是生成的可执行目标文件
链接器工作
- 符号解析
- 指的是目标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的就是把每个符号的引用正好和每个符号的定义关联起来
- 重定位
- 链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得他们指向这个内存地址
链接器解析多重定义的全局符号
-
强符号:函数名和已经初始化的全局变量
-
若符号:未初始化的全局变量
规则 -
不允许有多个重名的强符号
(这种情况下会报错,解决办法在其中一个前面加一个extern表示是从其他文件中引用过了的) -
如果有一个强符号和多个弱符号同名,那么选择强符号。
-
如果有多个弱符号同名,那么从这些弱符号中任意选一个。
注意:静态全局变量的作用于仅限于在本文件内
加载可执行目标文件步骤
- 每个程序运行都在一个进程上下文中,有自己的虚拟地址空间。当shell运行一个程序时,父shell进程会生成一个子进程,他是父进程的一个复制。子进程通过execve系统调用启动加载器。加载器会删除子进程现有的虚拟内存段,并创建一组新的代码、数据、堆、栈,新的堆栈段被初始化为0.通过将虚拟地址中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件的内容。最后,加载器跳转到_start地址,它最终会调用应用程序的main函数(除了一些头部信息,在加载过程中没有任何从磁盘到内存的数据复制,直到cpu引用时,采用置换算法换入)
静态链接
- 在编译的时候完成链接工作
- 每一份可执行文件都包含着一份标准函数集合的完全副本,浪费磁盘空间
- 当改变标准函数时都需要对整个源文件进行重新编译,十分耗时和难以维护