前言
📫作者简介:小明java问道之路,专注于研究计算机底层/Java/Liunx 内核,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计📫
🏆InfoQ签约博主、CSDN专家博主/Java领域优质创作者、阿里云专家/签约博主、华为云专家、51CTO专家🏆
🔥如果此文还不错的话,还请👍关注 、点赞 、收藏三连支持👍一下博主~
本文导读
一、计算机程序的可执行文件
1、动态连接
我们再看上面代码,代码中包含的 stdio.h 会告诉编译器,我们需要依赖这个函数定义的函数。因此编译器编译好的 a.out 文件中包含一个动态的链接符好 printf,当运行 ./.a,out 时,OS中动态链接器会发现该符不完整,还需要一个函数的地址,即 printf 的地址,这时链接器就会加载动态链接库,找到它的符号表,并发现文件中包含了 printf函数,于是就把原来调用 printf 的符号地址修改为动态链接库中的真实地址。
通过上图可以看到,gcc demo.c 命令默认是使用动态链接的方式来生成可执行文件,这时的文件中不完整,因为它需要的 printf 函数是没有执行体的,只有一个符号, 称之为符号地址,当它被 OS 加载到内存中后,会通过动态链接器将符号地址修改为指向动态链接库的 printf 函数地址。通过 ll -h 命令,我们可以看到 a.out 文件的大小为8KB。
2、静态连接
此时,读者肯定在想,为什么要在加载后由链接器找到真实的地址,而不是在编译生成aout文件时就将 动态链接库的代码包含其中呢?
相信这是大部分读者的想法。不过别着急,我们确实有办法让 a.out 在编译时就包含 printf 代码。这种方式被称为静态链接,可以通过可重定向文件 libc.a 实现,这个文件里同样包含 printf 的符号定义,但我们一般不这么做,我们看下静态编译的效果。
通过上图,描述了通过 gcc-static demo.c 命令生成可执行文件件,这时的文件就是完整的可执行程序,它不包含符号地址,并且都是指向真实函数执行地址,当 OS 将它加载进入内存后,可以直接执行,不需要通过动态链接器对它进行链接。
但是,我们却发现它的大小居然有841KB。这个大小远大于使用动态链接生成的可执行程序。
此时,读者就知道二者之间的区别和使用选择了,假设有 100个程序,都使用动态链接库函数,如果用动态链接,这100个线程可以共享这一个动态链接库。换而言之,内存里只会存在一份 printf 代码,100个线程共享。但如果我们选择静态链接呢?将会造成内存里存在100份相同的printf代码。这肯定不好,占用内存,还得不到什么好处。
动态链接这么好,我们是否可以在任何场景下都用它呢?肯定不可以。静态链接虽然有缺点,但存在即合理。当开发者不想让使用者提供动态链接库时,可以直接给它一个静态链接生成的应用程序,这样使用者就不用再去安装依赖的动态链接库,直接执行即可。同时,也可以避免使用者发现开发者依赖了哪些动态链接库,进而推敲代码的功能和特点。