Linux——gcc/g++编译器
Linux ——gcc/g++的编译链接过程,目标文件对动静态链接方式,动静态库的区别
目录
I.Linux编译器
1.gcc/g++编译器
gcc是用来编译C语言代码的编译器,而g++是用来编译C++代码的编译器的。
而gcc和g++都是软件,需要使用yum进行下载
注:需要使用root权限才能下载
在C代码生成可执行程序的过程中,会有四个过程:
1预处理,2编译,3汇编,4链接
预处理:从test.c开始,该代码文件会通过预处理后便停下来,形成test.i文件(头文件展开,去注释,宏替换,条件编译)。
编译:从test.i开始,该代码文件会转换成汇编语言,形成test.s文件。
汇编:从test.s开始,该代码文件会转换成计算机能读懂的二进制文件,形成test.o文件。
链接 :将多个test.o文件由链接起绑定在一起,形成单一的可执行程序,并且与C代码库中的函数一起。
Linux对.c文件分辨进行预处理,编译,汇编三大步指令:
gcc -E test.c -o test.i (预处理指令)
gcc -S test.i -o test.s (编译指令)
gcc -c test.s-o test.o (汇编指令)
第四个链接过程指令不需要写:
默认版的链接指令:gcc test.o
因为操作系统会默认从特定目录中找所需要的第三方库,若系统库中没有,则需要使用-l选项去链接:
完整版的链接指令:gcc test.o -l [第三方库]
预处理指令:
gcc -E test.c -o test.i (预处理指令)
预处理所做的工作就是:编译器将.c文件中的头文件从操作系统库中找出来,然后拷贝头文件的内容到一个.i文件中,相当于展开头文件;此外将定义好的宏,条件编译等指令带入代码中;将.c文件中的注释全都注释掉(计算机不需要知道你写的注释,没用!它只需要代码)
通过上图可知:.i文件的行数达到了近900行,百分之90多的内容全是展开的头文件的内容
编译指令:
gcc -S test.i -o test.s (编译指令)
这个过程所做的工作就是:编译器对代码进行语法,词法,语义的分析,将代码从C类型转换为高级汇编型语言。
汇编指令:
gcc -c test.s-o test.o (汇编指令)
该过程所做的工作就是将汇编代码转换为01二进制代码,直到这一步,计算机才能真正读懂我们的代码。
二进制内容对我们来说就是看不懂的乱码!
此时,虽然该文件已经能被计算机所读懂,但是还仍不能被运行,缺少了一步链接过程。
整体来说就是:test.c ---> test.i ---> test.s ---> test.o二进制文件
接下来说一说链接过程:
在我们编写的C代码程序中,并没有定义“printf,scanf”等函数的实现方式,在预编译过程中包含的“stdio.h”头文件中也只有该函数的声明,而没有定义函数的实现,那么,那么是在哪里实现“printf”函数的呢?
其实是系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc编译器会到系统默认的库搜索路径“/usr/lib”下进行查找,也就直接链接到 libc.so.6 库函数中去,这样就能实现函 数“printf”了。
链接的本质: 无非是我们调用函数的时候和标准库进行关联,从而实现我们写的程序文件
我们使用链接库,有两种方式:1.动态链接 2.静态链接。
II.动静态链接
一.动态链接
动态链接是把程序按照模块拆分成各个相对独立的部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。下面简单介绍动态链接的过程:
假设现在有两个程序program1.o和program2.o,这两者共用同一个lib.o,假设首先运行程nrram1,系统首先加翻o0ram1.0当系统发现program1.o中用到了lib.o,即program1.o依赖于lib.o.那么系统接着加载lib.o,如果program1.o和lib.o还依赖于其他目标文件,则依次全部加载到内存中。当program2运行时,同样的加载program2.o,然后发现program2.o依赖于lib.o,但是此时lib.o已经存在于内存中,这个时候就不再进行重新加载lib.o,而是将内存中已经存在的lib.o映射到program2的虚拟地址空间中,从而进行链接(这个链接程和静态链接类似) 形成可执行程序。
动态链接基本思想:对组成程序的目标文件等到程序运行的时候才进行链接也就是说,把链接这个过程推迟到了运行时再进行,这就是动态链接的基本思想。
优点就是:形成的可执行程序小,节省内存和磁盘占比空间。
二.静态链接
静态链接,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同个目标文件都有依赖,例如多个程序中都调用了print0函数,则这多个程序中都会含有printf.o的底层实现方式,所以同一个目标文件都在内存中存在多个副本,原本一个目标文件只需要一个函数的.o实现文件,现在由于其他目标文件也有相同于自己文件中的函数,产生多个冗余的函数.o文件;还有就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
所以动态链接能解决这些问题: 弥补了静态链接造成的浪费空间的缺点。
三.两个链接的区别:
动态链接的优缺点:
优点1:即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分副本,而是这么多个程序在执行时共享同一份副本;所以最终形成的可执行文件体积小。
优点2:更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。
缺点1:无法独立运行;可移植性差。
静态链接的优缺点:
优点1:可移植性强;可独立运行。
缺点1:代码冗余,可执行文件体积大。
缺点2:如果所有使用的静态库发生改变,程序需要重新编译。
据估算,动态链接和静态链接相比,性能损失大约在5%以下。经过实践证明,这点性能损失用来换区程序在空间上的节省和程序构建和升级时的灵活性是值得的。
III.动静态库
采用静态链接方式实现链接操作的库文件称为静态链接库; 用动态链接方式实现链接操作的库文件称为动态链接库。
在 Linux 发行版系统中,静态链接库文件的后缀名通常用 .a 表示,动态链接库的后缀名通常用 .so 表示。
GCC 编译器生成可执行文件时,默认情况下会优先使用动态链接库实现链接操作,除非当前系统环境中没有程序文件所需要的动态链接库,GCC 编译器才会选择相应的静态链接库。如果两种都没有(或者 GCC 编译器未找到》,则链接失败。
当我门在编译C/C++代码的文件时,Linux默认会使用C/C++动态标准库进行链接,系统中是没有静态库的,需要我们亲自安装,如下是C,C++静态库的安装指令:
安装C语言静态库的指令: sudo yum install -static
试验1:
注意: gcc98k.c指今不带后面的-o..,系统会默认生成a.out(98k.c的可执行文件),若带了-o,就必须要写自己命名的可执行文件名字。
通过查看两个方式生成的可执行文件的大小:
更加证明了动态链接要比静态链接的性价比好的多。
安装C++静态库的指令:
sudo yum install -y libstdc++ -static
使用g++编译器对.cpp文件进行编译,转换成可执行程序的指令:
试验2:
file指令:
功能说明:辨识文件类型。
语法:file [选项] 文件或目录...
例1.使用file指令查看上面创建的两个动静态链接生成的可执行文件类型:
ldd指令:
功能说明:用来打印或者查看程序运行所需的共享库(共享库就是动态库!)。常用来解决程序因缺少某个库文件而不能运行的一些问题。
语法:ldd [文件名]
如下:使用ldd指令查看可执行文件:
总结:
Linux下默认形成可执行程序,默认使用的是动态库。动态库不需要下载(成本低,效率高) ! 只有静态库需要下载。
在linux下库的命名:
动态库的类型: libXXXXXXX.so
静态库的类型: libyyyyyyy.a
完整的C动态库,大小2156492字节,全名: libc-2.17.so:
完整的C静态库,大小为5105516字节大小,全名: libc. a:
所以:我们写的代码 + 库提供的代码=可执行程序
此外,vim编辑器不仅可以写C,C++语言的代码,还可以写Java,Python语言的代码哟!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)