如果你想了解程序如何编译、链接,动态库、静态库如何加载以及可执行文件生成过程,推荐俞甲子老师、石凡老师以及 潘爱民老师编著的《程序员的自我修养-链接、装载和库》。虽然相关知识并不能直观的提升编程技能,但是对于程序编译、运行过程中遇到的问题,能够更快、更深入的定位问题的原因。写这篇文章,一部分是为了和各位分享知识,一部分是为了网络上做备份。正文内容部分来自书籍,部分来自理解,可能有理解错误的地方,希望能够通过私信或评论的方式给我矫正和建议。
北桥 作用:高速数据交换 涉及硬件: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生成汇编文件
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
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回车,即可提示选项。
函数和变量修饰符
随着项目工程代码量越来越大,为防止函数重定义的冲突,各编译器生成目标文件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格式文件命令。
例子二:在阅读源码时,经常能够看见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共享库版本和查找过程
Windows下动态链接创建
创建动态库dll cl /lDd xxx 创建调试版本的动态库
被外部使用的导出函数使用__declspec(dllexport) 修饰
使用外部引用的函数__declspec(dllimport)修饰
创建动态库dll时,会产生后缀名exp结尾的文件,该文件是链接器创建dll时的临时文件(包含导出表的信息)
函数调用惯例
程序执行过程
1)运行库入口函数_start.XXX
2)环境初始化:堆、IO、线程、全局变量构造、环境变量填充argc,argv...
执行这一步之后,才能在函数体中调用malloc,new等
3)main函数体执行
4)清理工作:堆、IO。。。
小结
gcc GCC编译器 cl msvc编译器
ld GNU链接器 link msvc链接器
objdump GNU文件查看器 dumpbin PE文件查看器