【Linux】深入Linux:GCC/G++编译器实用指南
一、Linux编译器-gcc/g++使用
从普通源文件形成可执行程序文件,需要经历四个阶段:预处理(进行宏替换),编译(生成汇编指令),汇编(生成机器可识别代码),链接(生成可执行文件或库文件)
在Linux下,我们可以通过使用gcc/g++对普通源文件进行以上四个步骤处理,形成可执行程序。gcc是专门来编辑C语言代码,g++可以用来编辑C语言和C++语言的代码,g++底层是通过gcc实现。
在使用方面上,gcc和g++相差不大。在这篇文章中将编辑C语言代码使用gcc进行编译工作。接下来,进行相关知识的讲解。
【使用示例】
当使用编译器编译源文件会生成可执行程序,该可执行文件默认取名为a.out,当然我们可以使用-o 选项对目标文件重命名,但是确保需要形成一个可执行程序,该目标文件后缀需要符合可执行文件的后缀。
在使用gcc/gcc进行代码编译时,还需要注意当前编译器版本是否支持相关特性。如果由于编译器版本过低,会导致代码无法编译成功。对此我们在网上搜索安装最新gcc/g++指令或者在使用过程中添加选项gcc test.c -o my.exe -std=c99及g++ test.cpp -o my.exe -std=c++11。
虽然Linux不考虑文件后缀,但是不意味着Linux开发工具不考虑文件后缀。我们需要知道g++底层还是gcc,g++可以编译C/C++代码,但是gcc只能编译C语言代码,gcc不认识C++这些流,大部分是编译错误。
二、gcc编译可执行程序具体过程
使用gcc可以直接帮我们形成可执行程序,今天我们想看下从源文件到可执行程序每一个步骤。这里需要使用到一些gcc指令,能够使得他在每个阶段停下来并且能让我们看见中间的编译结果。
2.1 预处理阶段
指令:gcc -E test.c -o test.i
在预处理阶段,会该文件进行宏替换,去注释,头文件展开,条件编译等操作。
预处理指令是以#号开头的代码行。
选项“-E”,该选项的作用是让 gcc 在预处理结束后停止编译过程
选项“-o”是指目标文件,“.i”文件为已经过预处理的C原始程序
2.1.1 条件编译
条件编译:通过指明的编译条件,实现对于代码的动态裁剪。
条件编译可以运用在某个软件的社区版和专业版。社区版是专业版子集,如果社区版出现程序bug会影响到专业版,那么需要维护两份代码,但是可以通过条件编译,去动态的裁剪,这里只需要维护一份代码就可以了。
【具体示例】
【配合-D选项,命令行宏定义】
通过-D选项命令行宏定义,编译器可以直接动态的直接向源代码添加宏。使用命令式的宏,可以进行更加方便裁剪,根本不需要修改源代码,实现动态剪切功能。
在整个软件是某个功能的函数,如果想添加某个函数,在对应的代码块里面添加对应的功能就行了。
2.2 编译阶段
指令:gcc -S test.i -p test.o
在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言
用户可以使用“-S”选项来进行查看。该选项只进行编译而不进行汇编,生成汇编代码,当编译C语言翻译完毕成为汇编语言就停下来
这里被处理文件可以是该阶段以上的文件,无非是重新走一步流程。
2.3 汇编阶段
指令:gcc -c test -o test.obj
通过汇编阶段会形成目标文件,全称为可重定位目标文件。这种文件不能直接执行,需要形成可执行程序exe文件
由于该文件是并不是一个可执行文件,而是二进制目标文件,属于临时文件是不能被直接执行的。
读者在此可使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码
2.4 链接阶段
指令:gcc test.o -o my.exe
在实际使用上,对于上面源文件通过gcc具体编译可执行程序流程,我们不需要去细致的编写,直接gcc直接形成可执行程序即可。以上需要观察可执行程序形成过程中的选项,可以根据键盘上Esc记忆。
三、函数库(动态库与静态库)
函数库一般分为:静态库和动态库两种
我们C程序中,并没有定义printf的函数实现,且在预编译中包含的stdio.h中也只有该函数的声明,而没有定义函数的实现。那么是在哪里实printf函数的呢?
是由于系统把这些函数实现都被保存到名为libc.so.6的库文件中,在没有特定指定时,gcc会到系统默认的搜索路径/use/lib下进行查找,也就是链接到libc.so.6 库函数中去,这样就能实现函数printf 实现,这里是链接的作用
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为.a
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为.so,如前面所述的libc.so.6就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,如下所示。 gcchello.o –o hello
gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证
我们可以通过lld指令后面跟着可执行程序,能查看该可执行程序所依赖的库,一般默认是动态链接,所以展示链接到动态库,其中这些都是打包好的纯二进制文件。
链接过程实际上就是把我们写的源代码和库中方法链接形成一个可执行程序,如果需要使用某个办法,那么可以直接调用别人写好在库中好办法。
【不同平台下库的后缀】
【在Linux下】:
动态库:.so
静态库:.a
【在Windows下】:
动态:.dli
静态:.lib
【得到库的真实名字】
比如:libc.so.6,我们将前缀和后缀删除,就得到c,说明了libc.so.6是C标准库。虽然gcc/g++没有头文件作为连接,但是它们默认是可以找到的。
四、图示理解动动态与静态库及其对应链接
由于存在动态库和静态库,在连接这两种库也存在对应的两种连接方式:动态连接与静态连接
【小故事】
你是个新生(源文件),在开学的时候问学长(编译器),我们学校(内存)附近是否有网吧(动态库)。学长(编译器)告诉了你目标库的地址(动态链接)。这几天你(源文件)有事情可以根据学长告诉你的地址(动态链接)去网吧(动态库)找学习资料(方法),你室友(多用户)一听,很开心也按照目标地址(动态链接)也去网吧(动态库)找学习资料(方法)。突然有一天,接到举报,派出所将这网吧(动态库缺失)查封了,这也意味着网吧里面学习资料就找不到了,学长跟你说的地址也不能去了(动态链接失效)。共享动态库,但是一旦动态库缺少,所有的动态链接,库中程序,都会无法运行
有一天,你跟你爸说,爸我想在学校(内存)玩电脑找下学习资料吗。由于你老爸跟校长认识,你学习成绩也是不错的。你爸就找到那家网吧的老板(静态库),买了一台机子(拷贝动态库方法到可执行程序中)给你安在宿舍里面。你宿舍一看,也跟自家老爸一说,你们宿舍基本人手一台电脑,网吧倒闭也不关你们的事情了。在编译时候,把库中方法,拷贝到自己可执行程序中,通过静态链接访问(起床就可以玩电脑),不关心任何库。
五、动态库与动态链接
如果采用动态链接访问动态库,意味着,在内存当中未来加载动态库,只需在内存中加载一份动态库,所用程序如果有需要,跟函数调用一般使用,相较于静态库最大优点节省空间
六、静态库与静态链接
如果采用静态链接访问静态库,意味着,静态链接会默认把库中代码拷贝到程序中,也是说静态链接形成的可执行程序不依赖任何的库。
6.1 静态存在空间浪费问题
比如需要实现printf方法,静态链接将在每个程序中将printf方法拷贝一份,同时需要加载内存当中给可执行程序拷贝一份,会导致数据冗余情况发生。
对于静态链接,还存在个别空间浪费,这不仅仅体现的可执行程,还有占磁盘空间,它未来还要加载到内存中,也比较浪费内存空间。
6.2 生成静态链接的可执行文件
【问题】:该程序依赖x库,你的机器上有x库,你的程序可以跑,但是单纯把你的程序拷贝到另一台机器上,另一台机器没有x库,就没办法跑起来了。
这个时候需要一个非常好的跨平台性的可执行问下,将你的程序静态链接,你的可执行程序二进制代码直接部署到其他机器上,我们的程序不依赖任何的动态库,不用做更多的环境检测,直接安装就行了。
【-static选项】:
生成静态链接的可执行程序选项-static。其中gcc -o mytest-static -static 该选项的作用就是,编译时候需要链接相关静态库和可执行程序。
生成一个静态链接的可执行文件:所有所需的库都被嵌入到该文件中,不依赖外部的共享库(动态链接库,如 .so 文件)。这样,即使在目标系统上缺少这些共享库,可执行文件仍然可以运行。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/2302_79177254/article/details/143167957
版权声明:
作者:SE_Wang
链接:https://www.cnesa.cn/2915.html
来源:CNESA
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论