编译、装载和库那些事-《程序员的自我修养-链接、装载和库》总结(读后感)

         如果你想了解程序如何编译、链接,动态库、静态库如何加载以及可执行文件生成过程,推荐俞甲子老师、石凡老师以及 潘爱民老师编著的《程序员的自我修养-链接、装载和库》。虽然相关知识并不能直观的提升编程技能,但是对于程序编译、运行过程中遇到的问题,能够更快、更深入的定位问题的原因。写这篇文章,一部分是为了和各位分享知识,一部分是为了网络上做备份。正文内容部分来自书籍,部分来自理解,可能有理解错误的地方,希望能够通过私信或评论的方式给我矫正和建议。

       北桥      作用:高速数据交换  涉及硬件:CPU、内存...             南桥      作用:低速设备数据交换    涉及硬件:键盘、磁盘...

       内存利用从一开始的分段=> 分页

写时复制(COW):两个任务同时自由的读取内存,其中任何一个试图修改内存数据时,就复制一份内存单独使用

        集成开发环境(以下Linux使用gcc,windows使用VS),将编译、链接称为构建,集成开发环境将具体细节隐藏,使得我们更专心程序开发。程序从源码变为可执行文件,其实分为四个步骤:

  • 预编译/预处理

       处理头文件,宏定义,条件编译等

       Linux:gcc -E hello.c -o hello.i     Windows:cl /P hello.c

  • 编译

       词法分析,语法分析,语义分析。。。

       词法分析:解析单个词汇;语法分析:生成语法树;语义分析:加入变量等类型到语法树    产生汇编文件

      Linux: gcc -S hello.i -o hello.s    Windows:cl /c hello.c 

       注:编译由c语言生成的hello.i时,用这种方式编译成功,但如果如果文件是cpp文件生成的,用gcc -S hello.i -o hello.s出错(我也不知道出错原因~。·~),可以使用gcc -S hello.cpp -o hello.s生成汇编文件

扫描二维码关注公众号,回复: 4674127 查看本文章

      Windows下好像无法直接生成汇编文件,再通过汇编生成机器指令文件。/c直接生成机器指令文件obj。不过可以通过cl /Fa hello.c生成汇编文件asm,也可以通过dumpbin /DISASM hello.obj查看汇编文件或导出为汇编文件。

  • 汇编

      汇编指令与机器指令的映射操作    产生机器指令文件

     Linux: gcc -c hello.s -o hello.o   Windows:cl /c hello.c 

  • 链接

     目标文件链接成可执行文件exe或out

     Linux:ld xxx.o xxxx.so;Windows:link   xxx.obj xxxx.dll

图1:COFF格式及变种

COFF格式

     我们经常遇见的.exe、.dll、.lib、obj、so等后缀文件,其实都是COFF格式的变种。Windows下在COFF格式基础上扩展为PF;Linux下在COFF格式基础上扩展为ELF。而COFF格式内容格式如上图1所示(只列出了主要的格式)。PE和ELF格式文件内容与COFF格式类似。

     如何查看以上文件格式内容呢?Windows平台提供dumpbin程序,dumpbin应用程序在VS的VC目录和cl编译器在同个目录下;Linux平台下提供objdump程序,objdump终端即可使用。dumpbin程序想要在终端直接使用,需要在path环境变量中配置。两个平台在终端直接键入dumpbin或objdump回车,即可提示选项。

图2:查看COFF格式文件

函数和变量修饰符       

       随着项目工程代码量越来越大,为防止函数重定义的冲突,各编译器生成目标文件obj对源码中函数和变量进行修饰。各编译器修饰规则不一样,导致各编译器生成的目标文件obj,没法混合链接,形成了二进制级别ABI的不兼容。Windows平台下VS编译C语言32位程序,添加修饰符下划线;编译C语言64位程序,不添加下划线。Linux平台下gcc编译C语言,不添加下划线。对于C++的修饰方式更加复杂,但修饰后的函数和变量32位程序也会有下划线,64位程序则没有。

例子一:oracle的oci库分为32位和64位,如果你在VS的32位平台,使用64位的OCI库,链接提示未定义错误。因为VS32位程序编译后有修饰符下划线,而64位的oci动态库的导入库,没有下划线,链接器不识别,所以未定义错误。这就是32位应用程序必须使用oci32位的库。查看导入库内容和目标文件内容可以使用查看COFF格式文件命令。

图3:OCI库与应用程序

例子二:在阅读源码时,经常能够看见extern "C"{}这种写法,主要是为了C++程序能够使用C库。下面从修饰符的角度描述,在cpp文件中使用到C库的A函数,cpp生成的中间文件obj将采用C++修饰符修饰,而C库的A函数导入库lib中采用C修饰符修饰,导致链接失败。将使用的函数使用extern写法包括,将使编译器知道,中间的函数使用C修饰符修饰,链接器链接可识别,链接成功。同理,也可以通过这种方式实现C++实现的库被C语言使用。

静态链接和动态链接

       静态链接库和动态链接库可以理解为中间文件obj的集合。在单独编译文件时,生成中间文件,而源文件中有些内容引用的是其余文件,这时编译器就会将外部引用的内容对应的地址做个标记,标识是外部引用的内容,地址设置位0x000000。

        对于静态链接,生成可执行文件,在链接的过程中,链接器起到重定位的作用,即将值为0x000000的地址,根据其余链接过来的文件重新设置地址,如果这个链接过程中,内容无法重定位,则链接出错,未定义。所有的中间文件,库文件等全部被写入到可执行文件中,所以静态链接生成的库较于动态链接来说大。

       对于动态链接,链接生成exe,生成可执行文件必须确定引用函数性质(猜测此时用到导入库.lib),链接器将引用函数标记为动态链接符号,但此时的可执行文件并没有包括依赖的库文件,即可执行文件地址还存在0x000000,未重定位,需要在运行的时候加载依赖的库dll,即重定位的操作放在了运行时。

      静态链接较于动态链接的缺点是1)可执行文件大 2)依赖库一旦更新,所有用到的项目工程需要重新编译

Linux共享库版本和查找过程

图4:Linux下共享库路径

Windows下动态链接创建

     创建动态库dll    cl /lDd xxx 创建调试版本的动态库

     被外部使用的导出函数使用__declspec(dllexport) 修饰

     使用外部引用的函数__declspec(dllimport)修饰

     创建动态库dll时,会产生后缀名exp结尾的文件,该文件是链接器创建dll时的临时文件(包含导出表的信息)

函数调用惯例

图5:常用的函数调用方式

程序执行过程

1)运行库入口函数_start.XXX

2)环境初始化:堆、IO、线程、全局变量构造、环境变量填充argc,argv...

      执行这一步之后,才能在函数体中调用malloc,new等

3)main函数体执行

4)清理工作:堆、IO。。。

图6:程序调用路径

小结

gcc   GCC编译器                                             cl    msvc编译器

ld      GNU链接器                                             link msvc链接器

objdump GNU文件查看器                                dumpbin PE文件查看器

猜你喜欢

转载自blog.csdn.net/zxgmlcj/article/details/81367474