151-C++代码的编译和链接原理

C++代码的编译和链接原理

我们在64位的linux Ubuntu系统演示。
我们看下面例子:在这里插入图片描述
我们看下面示意图
在这里插入图片描述

#开头的 都是在预编译阶段处理,进行展开的!!!

但是下面是特例:
#pragma lib的意思是当前程序运行时需要链接的库,必须存活在链接阶段
#pragma link的意思是程序运行以后直接以指定函数为入口函数(默认的入口函数是main函数 ),也是存活在链接阶段

编译阶段:语法语义词法分析,代码的优化;处理完之后,生成相应平台的汇编代码(gcc/g++ 后面加参数 )

汇编阶段生成.o可重定位二进制目标文件,也就是把汇编码转成相应平台的机器码
链接阶段:所有.o文件和静态库文件合在一块进行链接。

链接分为2步:
在这里插入图片描述
链接后,生成可执行文件
gcc -o可以指定程序的名称

在这里插入图片描述
在这里插入图片描述

linux举例分析

1、编译过程

在这里插入图片描述

objdump可以查看.o文件和可执行文件的详细信息。

我们查看符号表:
在这里插入图片描述
如果当前文件引用外部文件的函数或者全局变量的符号时,在当前main.cpp编译成main.o的文件,这个符号会不会产生呢?
在这里插入图片描述
是会产生符号的,因为不产生符号就使用不了。

符号就是下图中的最右边这一列:
在这里插入图片描述

main函数和全局变量data分别放在.text段和.data段!
在这里插入图片描述
在这里插入图片描述
最左边的UND就是这个符号现在在代码上用到它了,但是却不知道它们是怎么定义的,所以只能给UND,是对符号的引用,不是对符号的定义。
在这里插入图片描述

  • l是local的意思,在当前文件看得见,g是global的意思,在其他文件也看得见。
  • 链接的时候是所有obj文件在一起链接的,所以对于链接器来说,只能看得见.o文件的g符号,对于l符号链接器看不见。
  • 对于定义静态的全局变量或者静态函数,只能在当前文件可见,其他文件看不见。
  • 编译生成的.o文件里面,普通的都是global,静态的都是local。
  • 所以,在多个文件可以定义名字相同的静态全局变量或者静态函数
  • 但是在多个文件不可以定义名字相同的普通全局变量或者普通函数,符号解析就有冲突了。

在这里插入图片描述
在这里插入图片描述
可以看到sum函数和gdata变量都已经定义出来了。

main函数:
在这里插入图片描述
sum函数:
在这里插入图片描述
符号表就是汇编器在将汇编码转成最终的.o文件的时候,会给文件生成符号表,不仅会生成符号表,还会生成各种段!
在这里插入图片描述
在这里插入图片描述

这个文件头我们查看看:
在这里插入图片描述
不是一个可执行的文件,是一个可重定位的文件。

注意:symtab存放的就是符号表

将常见的段都打印出来:
在这里插入图片描述
将所有的段都打印出来:
在这里插入图片描述
编译过程中,符号是不分配虚拟地址的(因为都不知道符号在哪里定义的)。是在链接的时候分配地址的
在这里插入图片描述
在这里插入图片描述
上面看不出来信息,我们需要在编译的时候加上调试信息:
在这里插入图片描述
在按上面的objdump -S main.o打开:
在这里插入图片描述
再查看sum.o文件:
在这里插入图片描述
我们看mian函数:
在这里插入图片描述

  • 在编译过程中,符号没有分配虚拟地址,但是指令已经分配好了。
  • 因为符号的地址不确定,所以将在指令上,将符号的地址都填充为0。
  • 后面再将这些符号的地址改成正确的地址。

编译过程重要的部分:

  • 符号表
  • 最终生成的.o文件都放了哪些东西

2、链接过程

所有的.o文件都长一样,各种段:
在这里插入图片描述

链接第1步:段的合并

在这里插入图片描述

main.o,sum.o在链接合并的时候,各个段全部进行合并:
在这里插入图片描述
在这里插入图片描述

所有.o文件段合并之后,就到了符号表的合并,进行符号解析;

  • 符号解析就是所有对符号的引用都要找到该符号定义的地方;
  • 就是* UND *找到其到底是在data段还是在text段;
  • 比如main.o文件中sum函数和gdata都是UND的,要在其他.O文件中的符号表中找一下,没找到的话,链接器就会报错;若果找到多个文件都有定义,链接器也会进行报错。(因为符号定义只能在某个.o文件出现1次,但是可以多个地方引用
  • 可能会报错(符号未定义,符号重定义)

段的合并,符号表本来就是.o文件的一个段,符号表段在合并的时候,就是符号解析,最终放在可执行文件里,可执行文件就是各种各样的段组成的。

链接第2步:符号重定向

符号解析成功以后:(所有对符号引用的都找到了符号定义的地方)

  • 给所有的符号分配虚拟地址
  • 分配完地址以后,所有符号都有地址了。
  • 然后到代码段上,指令上,原来填的符号地址都是0,现在要重新写上去。
  • 就是符号的重定向!!!

在这里插入图片描述
我们自己进行链接:

使用-e 指定入库函数:
在这里插入图片描述
在这里插入图片描述
所有符号都有其区域的位置和地址了!

我们查看代码段:
在这里插入图片描述
可以看到符号都有地址了:
在这里插入图片描述
对于sum来说,符号放的是偏移量

  • 符号是在链接过程的第1步:符号解析完成后(对所有符号的引用都要找到其定义的地址),分配虚拟地址。
  • 分配完地址,再跑到代码段的指令上把符号的地址填成符号的正确地址(符号的重定向)

3、可执行文件

打印符号表:
在这里插入图片描述
查看各种段:
在这里插入图片描述
可执行文件里面的符号:
在这里插入图片描述

打印可执行程序的文件头信息:

  • 里面记录了可执行程序的入口地址
    在这里插入图片描述
    将可执行文件中的代码段都打印出来:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

和可重定位目标文件不同的地方,可执行文件多了这么个段:
在这里插入图片描述注意这个program headers;

当我们运行可执行文件的时候,系统看program headers(程序头),Align是以页面大小对齐
在这里插入图片描述

  • 程序运行,符号表段,段表内容都不需要往内存上加载,只需要加载的只有代码段和数据段
  • 系统看program headers(程序头),上面00表示加载的是代码段,01表示加载的数据段。

可重定位目标文件*.o和可执行目标文件a. out的区别:

  • 组成的段大致相同
  • 可执行目标文件a. out中有program headers段,里面有两个load =》告诉系统运行这个程序的时候,把哪些内容加载到内存当中! ! !

在这里插入图片描述
可以看到只需要加载代码段和数据段
在这里插入图片描述

  • 程序的入口地址:可执行文件头有入口地址。
  • 运行的时候读文件头,从文件头的地址,加载的时候把这个地址加载到CPU的PC寄存器里,加载完成,CPU就从main函数的第一条指令地址开始执行了

4、可执行文件加载的过程示意图

在这里插入图片描述

  • 这就是整个程序从编译链接到运行的过程。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liufeng2023

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值