【Linux】gcc--Linux上最标准的编译程序

什么是编译程序(编译器)?
  编译器就是将“一种语言(通常为高级语言)”翻译为“另一种语言(通常为低级语言)”的程序。
  因为高级计算机语言便于人编写和阅读交流以及维护,而机器语言是计算机能直接解读和运行的。所以编译器将汇编或高级计算机语言源程序作为输入,翻译成目标语言机器代码的等价程序。这个翻译的过程称为编译
  一个编译器工作的主要流程包括以下三步:预处理、编译、汇编和链接。先总体来看:
这里写图片描述
上面的每一步都必须经过gcc编译器如下处理:
gcc [选项] 要编译的文件 [选项] 将生成的文件
预处理(进行宏替换)
  预处理功能主要包括:宏替换、文件包含、条件编译和去注释等。
  预处理阶段:gcc -E .c文件 -o .i文件
  选项“-E”的作用:使gcc在预处理结束后停止编译过程。
  选型“-o”是指目标文件(即该阶段将会生成的文件),“.-i”文件为已经过预处理的C原始程序。
编译(生成汇编代码)
  编译:gcc主要检查是否有语法错误和代码的规范性,以确定代码实际要做的工作,在检查无误后,gcc把代码翻译为汇编语言。
  编译阶段:gcc -S .i文件 -o .s文件
  选项“-S”的作用:使gcc在编译阶段结束,生成汇编代码后停止编译过程,不进行汇编。
汇编(生成机器可识别的代码)
  汇编:把编译阶段生成的“.s”文件转成目标文件。
  汇编阶段:gcc -c .s文件 -o .o文件
  选项“-c”的作用:查看已生成的二进制目标代码。
链接(生成可执行文件或库文件)
  链接阶段:gcc .o文件 -o [可执行文件名]
  在生成目标文件后,有的时候,还需要在程序当中引用和调用其他的外部子程序,或是利用其他软件提供的“函数功能”,此时就必须早编译的过程中将该函数库加入进去。这样,编译程序就可以将所有的程序代码与函数库做一个链接以生成正确的可执行文件。
什么是函数库?
  函数库:类似子程序的角色,可以被调用来执行的一段功能函数。
  举个例子:在C程序中,并未定义“printf”函数的实现,且在预编译中也只包含该函数的声明,那么“printf”函数是在哪实现的呢?原来:系统把这些函数实现都放置在了名为libc.so.6的库文件中,在没有特别指定时,gcc会到系统默认的搜索路径/usr/lib下进行查找,也就是链接到libc.so.6库函数中去,这样就实现了“printf”函数的并达到了链接的目的。
  函数库按照被使用的类型分为静态函数库与动态函数库两类。
  静态函数库(Static)
  静态库在编译的时候会直接整合到可执行程序当中,所以利用静态函数库编译成的文件会比较一些。这类函数库的后缀名通常为**“.a”
  当然,静态库也有其优点:编译成功的可执行文件
可以独立执行**,而不需要再向外部要求读取函数库的内容。
  关于静态库的升级:虽然可执行文件可以独立执行,但因为函数库是直接整合到可执行文件中,因此若函数库升级时,整个可执行文件必须重新编译才能将新版的函数库整合到程序当中。即:只要函数库升级了,所有将此函数库纳入的程序都需要重新编译。
  动态函数库(Dynamic)
  动态库与静态库被整个捕捉到程序中不同:动态函数库在编译时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库(即在程序里面只有一个**“指向”的位置而已,当可执行文件要使用到函数库的机制时,程序才会读取函数库来使用),所以它的文件会比较一点。这类函数库的后缀名通常为“.so”gcc在编译时默认使用动态库,gcc默认生成的二进制程序是动态链接的。
  这类函数库所编译出来的程序
不能被独立执行**,因为当我们使用函数库的机制时,程序才会去读取函数库,所以函数库文件必须要存在才行,而且,函数库的所在目录也不能改变,因为我们的可执行文件里面只有“指标”,当要取用该动态函数库时,程序会主动去某个路径下读取,所以动态函数库不能随意移动或删除。
  关于动态库的升级:虽然这类型的可执行文件不能被独立执行,然而由于其具有指向的功能,所以,当函数库升级后,可执行文件根本不需要进行重新编译,因为可执行文件会直接指向新的函数库文件(前期是新旧版本的函数库文件名必须相同)。
Linux下更倾向于使用动态函数库,那么如何判断某个可执行的二进制文件含有什么动态函数库呢?
  这里要用到一个命令:ldd
语法:ldd [-vdr] 文件名
功能:
①“-v”:列出所有内容信息;
②“-d”:重新将数据有丢失的链接点显示出来;
③“-r”:将ELF有关的错误内容显示出来
这里写图片描述

举例说明gcc编译详细过程!
(1)新建一个(.c)程序文件,编写一段程序代码(源码):
这里写图片描述
(2)直接进行编译:
在这里插入图片描述
产生一个编译成功的可执行文件a.out,即该文件内的内容就是机器可识别的语言:
在这里插入图片描述
(3)开始执行该可执行文件a.out:
①该文件所在位置(绝对路径):
在这里插入图片描述
②相对路径:
在这里插入图片描述
上面两种执行方式所得结果相同。
在默认状态下,如果我们直接以gcc编译源码,并且没有加上任何参数,则执行文件的文件名会被自动设置为a.out,所以就能直接执行./a.out这个文件。
那么,若我们想自己对产生的可执行文件命名,该如何进行编译?
在这里插入图片描述
如上图:我在原有的命令之后加上了“-o”选项,该选项作用是将文件输出到文件,然后将可执行文件命名为ret,最后执行该文件,得出了相同的结果。 
  已知,gcc的编译过程分为四个阶段:预处理、编译、汇编和链接,下面利用源文件gcct.c详细说明这四个阶段:
(1)预处理:gcc -E gcct.c -o gcct.i
在这里插入图片描述
“-o”后面的文件名可随意指定。进行上面的第一步编译(成功)后产生了一个文件gcct.i,打开它(下图仅为其中一部分):
在这里插入图片描述
在这里插入图片描述
(2)编译:gcc -S gcct.i -o gcct.s
在这里插入图片描述
编译成功之后产生了一个文件gcct.s,打开它:
在这里插入图片描述
该文件中的内容就是编译生成的汇编代码。
(3)汇编:gcc -c gcct.s -o gcct.o
在这里插入图片描述
编译成功之后产生了一个文件gcct.o,打开它(下图仅为其中一部分):
在这里插入图片描述
此时,其中的内容机器已经可以识别,但是它还不能被执行,因为在源码中包含一个函数printf,该函数(库函数)依赖了操作系统中的函数库。上面我们已经说过,目标文件gcct.o要被执行,还应该告诉操作系统函数printf是如何实现的,即将该函数所在函数库链接过来,所以第四个阶段:
(4)链接:gcc gcct.o -o ret
在这里插入图片描述
链接成功!下面看一下文件ret链接的(动态)库的详细信息:
在这里插入图片描述
其中libc.so.6,“c”为库名,“lib”为库的前缀,“.so”为后缀,“6”为版本号。
  再想想,如果源码文件不只是一个,而有多个,此时应该如何对程序进行编译?
  下面编写了两个程序t1.c和t2.c,用t1.c去调用t2.c:
在这里插入图片描述
在这里插入图片描述
接下来将源码编译成可执行的二进制文件并执行它:
在这里插入图片描述
【说明】由于源码文件有时并非只有一个文件,所以无法直接进行编译。此时,就需要先生成目标文件,然后再以链接制作成二进制的可执行文件。此外,当更新了t2.c这个文件的内容,则只需重新编译t2.c来产生新的t2.o文件,然后再以链接制作出新的可执行文件即可,而不必重新编译其他未修改过的源文件。
  如果我们想让程序在编译的过程中具有比较好的性能或产生更详细的信息,可以加适当的参数:
(1)“-Wall”:产生编译过程中的所有警告信息。
在这里插入图片描述
(2)“-O”:在编译的时候,依据操作环境给予优化执行速度,会自动的生成目标文件,并进行优化。
在这里插入图片描述
(3)“-l”:加入某个函数库。
先看一段源码:
在这里插入图片描述
直接对其进行编译,没有成功:
在这里插入图片描述
警告信息:没有sin的相关定义参考值。这是因为C语言里面的sin函数是写在libm.so这个函数库中的,而在源码中并未将该函数库功能加进去,所以就需要在编译与链接的时候将这个函数库链接进执行文件里面。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值