备战2020校招日记----4.17

本文深入探讨了链接器的工作原理,包括符号解析、重定位、静态库与动态库的生成及使用,以及运行时链接的概念。通过实际案例,揭示了编程中常见的链接错误及其解决方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0.目标完成情况

  • LeetCode两道 ---- 没做,实在不想做啊,这东西一旦不做就不想做了。
  • 侯捷的视频---- 没看。
  • csapp第7章看完符号解析----- 超额完成,前11节都看完了,后面不打算看了。

1. 知识复盘

今天虽然被打扰了一个下午,但是总体还是非常开心的,因为我终于明白了一个困扰我很久很久的问题:include .c和include .h,有什么区别?哈哈哈哈,有一种顿悟的感觉,原来我遇到的那么多bug,都是和连接相关的,我太菜了。下面尽量默写吧,然后将我这几天画的思维导图上传。

1.1 符号解析

昨天说过,链接器主要完成两件事:符号解析重定位
其中我们日常遇到最多的错误恐怕就是符号解析的错误了。主要有两种:

  1. 未定义的引用
  2. 多次定义

对于第一种,一般都是声明了,但是没有实现,或者是没有合适的库。对于第二种,就涉及到include的问题了,我遇到的,就是把.c文件include进来,但是又编译了.c文件,那么就会导致两次定义。而符号解析的规则是不允许多次定义的引用的。这里涉及到强符号和弱符号的概念,以及建立在此基础上的符号解析规则。具体见我的思维导图吧,我说了会非常乱。

1.2 静态库

说完符号解析,先不慌说重定位的问题,因为此时可以引出一个非常重要的概念:静态库。静态库是一组二进制可重定位目标文件的打包集合,也就是说,可以认为静态库是一个文件夹(其实是.a文件),里面有很多.o文件,每个.o文件,对应于一个标准函数。在进行静态连接的时候,通常需要将静态库放在命令行后面(这里涉及到链接器的连接规则),链接器会选择静态库中有用的.o文件,将其复制到一个集合中,作为生成最终可执行文件的原材料。这里可以看出一点静态库的优点,也蕴含着静态库的缺点。缺点就是会发生数据和代码的拷贝,浪费磁盘,浪费内存。优点的话比较多,需要看书,或者看我的思维导图。

然后讲到如何生成静态库,并使用。还是挺简单实用的。

1.3 重定位

重定位涉及两个方面:各个节本身地址的重定位和节当中符号对应地址值的重定位。这个地方似乎没真正懂,已经忘了。。

1.4 生成可执行文件

可执行文件总体上和目标文件结构相同,就是将各个重定位之后的目标文件进行合并(因为已经有了所有符号的定义,所以可以进行合并),从而形成更大的一个一个的节。主要分为代码段和数据段,代码段主要是.text节,数据段主要是.data节和.bss节,此外还有一个段,这个段不会最终不会被加载到内存中,有一些特殊的用处,好像和动态链接器有关,忘了。

还有一个很重要的内容是,内存映射,我觉得应该说成程序内存模型,每个进程有自己的内存空间,其中最开始放的是代码段,然后放数据段,然后放堆,堆是向上增长的,而在内存的顶部,内核代码的下方,是放栈的,所以栈是向下增长的,而在堆和栈的中间,则是存放共享库的地方。

1.5 共享库(动态链接库)

1.2节提到了静态库的缺点,而动态库就是为了克服这个缺点,可以说克服地很彻底。共享库就是linux下的.so文件,在windows下就是.dll文件。共享体现在两个方面:

  1. 在磁盘中的可执行文件看来,动态库不会拷贝到任何一个可执行文件当中去,这和静态库相反。
  2. 在内存中的可执行文件看来,正在利用的动态库,可能还在“同时”被其他进程或者说可执行文件在使用。(这一点存疑,可能我理解有误)

加载时连接中,共享库的大概原理是,加载器在加载到一个头部数据的时候,会发现一个动态链接器的路径,然后就调用动态链接器,并把控制权交给动态链接器,动态链接器完成几个重定位(具体忘了)的工作,就在内存中得到了完整的可执行文件,从而可以执行。

然后讲了如何动手操作,来实现加载时连接,和静态连接的区别不是很大。主要是需要首先生成动态库,然后调用gcc的动态编译命令即可。

1.6 共享库的运行时链接

运行时链接更恐怖,直接让我们自己写加载链接库的逻辑,比如说加载哪个链接库,何时加载链接库,何时卸载库等。这就很牛逼了。因为,上一节中说的加载时连接,说白了,当我写的代码在被cpu执行的时候,他最终会加载连接哪个库,何时加载连接库(哎其实没有何时的问题,他一定是全部加载好然后才开始执行的),都是已经定死了的(他所谓的“动态”,只是相对于真正可执行程序的动态,相对于静态连接的动态,而不是相对于用户的动态),不会说因为我给他一个什么输入,他就换一个动态链接库来加载,那是做不到的。-----而运行时链接就可以。

实现运行时链接需要我们的应用程序调用一些操作系统的接口,来完成基本的加载链接库、定位符号地址、卸载链接库等操作。

运行时链接的优点有两个:

  1. 共享库的优点他都有,也就是节约空间balabala。
  2. 可以让应用程序来控制链接。这个优点非常重要,也比较难理解。应用程序来控制链接,意味着应用程序已经开始执行了,那么加载谁、链接谁,由这个正在运行着的应用程序说了算,所以我们可以在应用程序不停止的情况下更换链接库,而且应用程序自己也可以更换链接库呀,所谓 “我们更换”*和“应用程序更换”,其实是没有任何差别的。哎不能太啰嗦了。

2. 回答自己的问题

#include"tool.cpp" 和 #include"tool.h" 有什么区别?

首先, .h文件是永远不会被编译的,只有.cpp文件才会被编译。

.h文件存在的意义,就是替换到include的位置,编译器根本看不到你#include"tool.h"这几个字,他只能看到预处理之后的没有#include语句的.cpp文件。

.h文件存在的真正意义,是符号说明。编译器看到.h文件中的声明,就会生成一些符号,这些符号回头会由链接器来找到相应的实现(定义)。

那么,如果.h文件中写了很多函数的定义呢?这算什么?还是那句话,忘掉.h文件,只有.cpp文件,.h文件存在的意义,就是为了让我们的代码不要太长,分开写,方便管理,最终.h文件中的所有内容都会被复制到.cpp文件中,而编译器只看.cpp文件。

所以,include"tool.cpp"是一件很傻x的事情,虽然我有这么干过,而且还没报错。为什么傻x呢?因为在正常情况下,.cpp中放了一些函数的定义,这些定义如果被复制到#inlcude语句的地方,那么在所有的cpp文件中,就出现了两次定义,就出错了。

那为什么我之前这么做没报错呢?可能是我的.cpp中并没有任何定义吧,里面只有一些声明,所以在编译的时候没有问题,就当成一个全局引用了,链接的时候也没有问题,因为只要在这些声明的后面能找到他的定义就好了。总之就是负负得正的操作吧(微笑)。

最后:

  • 编译是针对所有.cpp文件生成一个一个.o目标文件的过程。
  • 链接是针对main函数所在的那个.o文件,将其他所有.o文件(可能包含库中的.o文件)链接过来并重定位,然后生成可执行程序的过程。

3. 明日目标

  1. 两道LeetCode,一定要做
  2. 看csapp第8章:异常控制流,尽量快点。和面试完全无关的最好跳过。
  3. 侯捷的C++视频看1个小时。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值