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

151-C++代码的编译和链接原理,第1张

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、可执行文件加载的过程示意图

  • 这就是整个程序从编译链接到运行的过程。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/868147.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存