C++(基础)———— 编译链接过程

我觉得在学习编译链接过程之前有必要了解一下虚拟地址空间。

虚拟地址空间

1、什么是虚拟地址空间?

 虚拟地址空间其实就是内存映射出来的存放地址的集合,它不是真实存在的,但又是可见的。

2、32位虚拟地址空间是多大?为啥?

 大小:4G     

 32位即就是32位地址总线(32条),一个位的地址对应一个字节的内存大小;32位地址总线所保存的地址( 地址都是用16进制表示)为

 0x 0000 0000  --》 0x ffff ffff。但是为了更清楚的解释问题,地址用二进制表述为   0000 0000 0000 0000 0000 0000 0000 0000  到

 1111 1111 1111 1111 1111 1111 1111 1111 ,由此可见一个虚拟地址空间保存的地址是2^32字节,所以它的地址空间的大小是4G。

3、虚拟地址空间图示如下:
C++(基础)———— 编译链接过程_第1张图片
用32位系统来说,每个进程都会产生一个4G大小的虚拟地址空间。

编译链接大致过程

4、运行main.c代码文件
代码如下:

 #include 
 #include 
 
 int data1 = 10;
 int data2 = 0;
 int data3;

 static data4 = 20;
 static data5 = 0;
 static data6;

 int main()
 {
     int a = 30;
     int b = 0;
     int c;
     
     printf("a= %d b= %d\n",a,b);
     return 0;
 }
                          

代码 --> 运行 (过程如下):

        代码 -->               编译 -->                  链接-->             运行

文件类型: .c                   .o                       .out

                              二进制可重定位文件       二进制可执行文件

5、编译过程:预编译 --> 编译 --> 汇编

预编译(生成 “.i” 文件)

  操作命令:gcc -E main.c -o main.i

  内部处理:处理宏定义    处理 # 开头的预处理指令   删除所有注释

编译(生成 “.s” 文件):进行词法分析、语法分析、语义分析及优化后产生相应的汇编代码文件

  操作命令:gcc –S main.i –o main.s

  内部处理:生成符号   生成汇编指令

汇编(生成 “.o” 文件):将汇编代码编程机器可执行的指令

  操作命令:gcc –c main.s –o main.o

  内部操作:将所有的汇编代码翻译成机器可识别的二进制指令

6、连接过程(这里的链接暂不考虑动态库和静态库的链接):将所有相关文件中的各个段,重新组合链接成新文件

  操作命令:gcc main.o –o main
  内部处理:合并段表   调整段偏移   合并符号表   完成符号的重定位

预编译(生成 “.i” 文件), 操作命令:gcc -E main.c -o main.i,生成的 .i 文件如下图
C++(基础)———— 编译链接过程_第2张图片 编译(生成 “.s” 文件),操作命令:gcc –c main.s –o main.o,生成的 .s文件如下图
C++(基础)———— 编译链接过程_第3张图片
汇编 (生成 “.o” 文件),操作命令:gcc –c main.s –o main.o,生成的 .o 文件如下图
C++(基础)———— 编译链接过程_第4张图片链接,操作命令:gcc main.o –o main,链接后生成的文件如下图
C++(基础)———— 编译链接过程_第5张图片

ELF文件

7、ELF文件的基本概念:

产生: 链接器链接后生成的最终文件为ELF格式可执行文件,一个ELF可执行文件通常被链接为不同的段,常见的段譬如.text、.data、.rodata、.bss等段。
内容:可执行文件、可重定位文件(.o)、共享目标文件(.so)、核心转储文件都是以elf文件格式存储的。
组成:文件头、段表(section)、程序头。

8、ELF文件的段:

一个典型的ELF文件包含下面几个段:

.text:已编译程序的指令代码段。

.rodata:ro代表read only,即只读数据(譬如常数const)。

.data:已初始化的C程序全局变量和静态局部变量。

.bss:未初始化的C程序全局变量和静态局部变量。

.debug:调试符号表,调试器用此段的信息帮助调试。

ELF文件格式如下图所示,位于ELF Header和Section Header Table之间的都是段(Section)。
C++(基础)———— 编译链接过程_第6张图片
9、查看ELF文件:

file 命令:查看文件头部信息
readelf 命令:读取ELF文件中信息
          -h:读取ELF文件头
          -S:读取段表

查看头部信息:
在这里插入图片描述查看权限:
在这里插入图片描述读取ELF文件头:
C++(基础)———— 编译链接过程_第7张图片

从上图可得:
Entry point addres:  程序的虚拟地址入口,因为这不是可执行的程序,故而为0 。
Start of program ... :  与上一行同理,这个程序没有program headers 。
Start of section... :   sections头开始出,这里208是十进制,表示从地址偏移0xD0处开始。
Size of this header :   ELF文件头的字节数。
Size of program headers:  不是可执行程序,故为0 。
Size of section headers:   section header的大小,这里每个section头大小为40个字节。
Number ... :  一共有9个section头 。

读取段表:
C++(基础)———— 编译链接过程_第8张图片

从上图中可得:
1、.data 大小为00000c (12), 12 / 4 = 3, 无误。
2、.bss  大小为000014 (20),20 / 4 = 5 ,无误。

符号、强符号和弱符号 :

符号:所有数据都会产生符号,指令只有函数名会产生符号。
强符号:初始化了的非静态数据。
弱符号:没有初始化的非静态数据。
弱符号在存储过程中不占 .bss 空间,因为其被强符号所替换。所以有时候 .bss 中少 的数据是因为强符号替换弱符号造成的。

链接详解

链接过程:

1、合并段表:将两个二进制可重定位文件中的段信息整合放到一个文件中。
2、调整段偏移:段表经过合并之后大小发生变化,所以地址会进行适当的偏移。
3、合并符号表:将多个二进制可重定位文件中的符号整合到一个文件中。
4、完成符号的重定位:链接器把每个符号定义与一个虚拟地址联系起来,然后修改所有对这些符号的引用,然后通过虚拟地址使得它们指向相应的存储位置。

图示如下:
C++(基础)———— 编译链接过程_第9张图片按照属性划分不同的段放在同一个页,运行时按照LOAD页信息将段按照页读入到虚拟地址空间
图示如下:C++(基础)———— 编译链接过程_第10张图片

你可能感兴趣的:(c/c++基础)