gcc和makefile篇

本文介绍了Linux入门系列,包括终端基本指令、yum包管理、vim文本编辑器、gcc编译器、makefile构建工具,以及程序的翻译、预处理、编译、链接过程。重点讲解了如何使用gcc和makefile进行项目自动化编译,强调了依赖关系在makefile中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux入门系列目录

《终端基本指令篇》
《yum 篇》
《vim 篇》
《gcc和makefile篇》
《Linux的第一个小程序——进度条》
《gdb 篇》



前言

在上一篇博客《vim篇》中已经了解vim的功能,学习vim的相关命令,最后还配置了vim,为编写代码提供了一个好的环境。
但是,无法编译运行的代码是没有价值的。
接下来,就学习如何使用将写好的代码编译,如何将文件运行,如何编写基本的makefile文件以提高写代码效率。

程序的翻译

在ANSI C的任何一种实现中,存在两种环境

第一种是翻译环境:指源代码被翻译成可执行的机器指令。
第二种是执行环境:机器指令被实际执行起来。

计算机能够识别并执行机器语言(即二进制指令),但是我们写出来的C语言代码都是文本信息,计算机无法识别,因此,我们要将源代码文件翻译成机器指令,而帮助我们执行这个过程的工具就是gcc(g++的操作与gcc高度相似)。

想要理解好gcc的使用,就先要对程序的翻译过程有一个良好的认知。

翻译的步骤可以分为2个大步骤或者4个小步骤:
2个大步骤指的是,编译链接,而编译又可以被分为预处理编译汇编3个小步骤,加上链接一共4个步骤。

简单过程如下图:
在这里插入图片描述

接下来就开始细讲翻译过程的每一步是如何操作的,讲解过程中用到的工具就是gcc,所以没装gcc的朋友可以先去装一个。
安装命令是sudo yum install -y gcc

预处理(预编译)阶段

C语言当中有一个知识点叫 “预处理指令”,指的是,以#开头的代码行。

而预处理指令的功能包括宏定义头文件包含条件编译等。

在预处理阶段gcc做的事主要有:宏定义的符号的替换、头文件展开、去除注释、条件编译这几个。

接下来个人写一个简单的小程序,命名为hello.c
再用一条命令gcc –E hello.c –o hello.i带你了解
选项-E,该选项的作用是让 gcc 在预处理结束后停止编译过程。
选项-o是指目标文件,-i文件为已经过预处理的C原始程序。


以下是文件对比图
在这里插入图片描述
由图中可以看到:
第一:hello.i文件中#include <stdio.h>不见了,取而代之的是一大段代码,这段代码正是头文件stdio.h的内容,这就是 “头文件的展开”。

第二:第4行的注释在hello.i文件中也消失了,这就是 “去注释”。

第三:宏定义的常量符号M在hello.i中被替换成了100,这就是 “符号替换”。

第四:如果去仔细比对stdio.h的原文件内容和hello.i的头文件展开的内容,就会发现,有一些代码在hello.i中没有了,所谓条件编译就是,代码在满足某种条件的情况下才会留下,否则就会在预处理阶段被直接删去

编译阶段

在这个阶段中,gcc 进行语法分析、词法分析、语义分析、符号汇总等操作(在《编译原理》中有详细介绍),在检查无误后,gcc 把代码翻译成汇编语言,即,生成汇编

命令为gcc –S hello.i –o hello.s
选项-S让 gcc 把C语言代码翻译成汇编后立即停止。


以下为文件内容对比图:
在这里插入图片描述
左侧为新生成的hello.s文件,右侧为原本的hello.i文件,如果了解过汇编语言就会发现,hello.s文件上的写的是汇编代码。

汇编阶段

在汇编阶段,gcc会进行两个操作:一、将汇编代码文件翻译成可被机器识别的代码(机器指令),新生成的文件叫 “目标文件”;二、每个文件都对应生成一个符号表,为链接阶段符号汇总做准备。

命令为gcc –c hello.s –o hello.o
选项-c是让gcc在将汇编文件翻译成机器指令后立即停止,


以下为文件内容对比图:
在这里插入图片描述
左侧为新生成的hello.o文件,右侧为原本的hello.s文件,一般我们的编辑器的编码方式为utf-8,在该编码方式下是不支持查看二进制文件内容的,能看到的只是一串乱码。

那么,有没有什么方法查看该文件内容呢?
答案是,有的。.o文件的格式为ELF,Linux上有个工具叫 “readelf”,可以帮我们查看该文件。

以下为使用命令readelf -s hello.o输入的内容。
在这里插入图片描述
虽然大多数的内容都不理解,但是右下角却有几个熟悉的符号 “global_val”、“main”、“printf”等等。这些就是 “符号表的内容”。

链接阶段

汇编阶段完成后,就进入翻译的最后阶段,“链接”。
链接就是把源文件中用到的库函数从函数库中拉取库函数实现代码,将其加载到一起,再合并段表和符号表以及重定位符号,最后形成一个可执行程序。

链接结果如下:
在这里插入图片描述

这里涉及一个重要的知识点,“函数库”。

函数库

我们写C程序时,并没有定义printf的函数实现,且在预编译中包含的stdio.h中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现printf函数的呢?

最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径usr/lib下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用。

静态库和动态库

  • 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为.a

  • 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为.so,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,如下所示。 gcc hello.o –o hello

  • gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证。

程序的运行

一般而言,生成可执行程序之后用到命令./+可执行程序文件名,如./hello,意思就是执行当前工作目录下的可执行程序hello

但是,在《yum 篇》时说过,指令、命令、工具等都是可执行程序,虽然叫法多,但他们是一个东西。而想要执行一个可执行程序,首先要找到这个可执行程序,然后再执行。

因此,实际上命令为文件所在的路径,而./+可执行程序文件名是为了方便,毕竟我们的项目文件一般都在一个目录下

在这里插入图片描述

gcc的选项

程序的翻译和与运行大致讲完了,这里就总结一下 gcc 常用的选项和功能:

  • -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
  • -S 编译到汇编语言不进行汇编和链接
  • -c 编译到目标代码
  • -o 文件输出到文件
  • -static 此选项对生成的文件采用静态链接
  • -g 生成调试信息。GNU 调试器可利用该信息。
  • -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
  • -O0-O1-O2-O3 编译器的优化选项的4个级别
    -O0表示没有优化,-O1为缺省值,-O3优化级别最高
  • -w 不生成任何警告信息。
  • -Wall 生成所有警告信息。

Linux项目自动构建工具 - make/makefile

当一个项目有多个源文件,如test1.c、test2.c、test3.c……时该怎么编译链接形成可执行程序?

方法一:

  1. 分别编译每一个源文件gcc -c test1.c test2.c test3.c……(自动生成对应.o文件)
  2. 再统一链接生成可执行程序gcc test1.o test2.o test3.o…… -o mybin

在这里插入图片描述
但是,如果源文件过多,如果说上百个,这个方法还好用吗?因此就有了方法二:也就是这篇博客的第二个重点,Linux项目自动构建工具 - make/makefile

背景

  • 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
  • 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。
  • makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
  • make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
  • make是一条命令makefile是一个文件,两个搭配使用,完成项目自动化构建。

依赖关系与依赖方法

依赖关系,通常指文件之间存在强相关。比如翻译过程中的hello可执行程序是有源文件hello.c一步步转换过来的,那就说明hello依赖hello.c

依赖方法,顾名思义,就是解决问题的方法。如果把源文件编译链接形成可执行程序这个过程当作是一件事,那么gcc test.c -o hello这条命令就是完成这件事的方法。

make与makefile的原理

说了那么多,那么make和makefile到底长啥样?实际上的作用到底是什么?

【makefile的基本内容}】
在这里插入图片描述

【makefile内容解释】
在这里插入图片描述

  • $^:依赖文件列表。
  • $@目标文件。
  • %.o:当前目录下的所有.o文件的展开。
  • %.c:当前目录下的所有.c文件的展开。
  • $<%.c多代表的源文件,一个一个的拿出来,用gcc进行编译,形成同名的.o文件。

【make命令使用】

[LJH@hecs-66624 LinuxLearning]$ ll
total 16
-rw-rw-r-- 1 LJH LJH   92 Aug 22 11:13 makefile
-rw-rw-r-- 1 LJH LJH 1157 Aug 21 19:43 mybin.c
-rw-rw-r-- 1 LJH LJH   33 Aug 21 19:33 mybin.h
-rw-rw-r-- 1 LJH LJH   85 Aug 21 19:39 test.c
[LJH@hecs-66624 LinuxLearning]$ make
gcc -c mybin.c
gcc -c test.c
gcc mybin.o test.o -o mybin
[LJH@hecs-66624 LinuxLearning]$ ll
total 36
-rw-rw-r-- 1 LJH LJH   92 Aug 22 11:13 makefile
-rwxrwxr-x 1 LJH LJH 8472 Aug 22 11:14 mybin
-rw-rw-r-- 1 LJH LJH 1157 Aug 21 19:43 mybin.c
-rw-rw-r-- 1 LJH LJH   33 Aug 21 19:33 mybin.h
-rw-rw-r-- 1 LJH LJH 2248 Aug 22 11:14 mybin.o
-rw-rw-r-- 1 LJH LJH   85 Aug 21 19:39 test.c
-rw-rw-r-- 1 LJH LJH 1384 Aug 22 11:14 test.o
[LJH@hecs-66624 LinuxLearning]$ make clean
rm -f *.o mybin
[LJH@hecs-66624 LinuxLearning]$ ll
total 16
-rw-rw-r-- 1 LJH LJH   92 Aug 22 11:13 makefile
-rw-rw-r-- 1 LJH LJH 1157 Aug 21 19:43 mybin.c
-rw-rw-r-- 1 LJH LJH   33 Aug 21 19:33 mybin.h
-rw-rw-r-- 1 LJH LJH   85 Aug 21 19:39 test.c

make 命令的原理:

  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。

  2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到mybin这个文件,并把这个文件作为最终的目标文件。

  3. 如果mybin文件不存在,或是mybin所依赖的后面的mybin.otest.o文件的文件修改时间要比mybin这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成mybin这个文件。

  4. 如果mybin所依赖的mybin.otest.o文件不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成mybin.otest.o文件。(这有点像一个堆栈的过程)

  5. 当然,你的C文件和H文件是存在的啦,于是make会生成 mybin.otest.o 文件,然后再用mybin.otest.o文件声明make的终极任务,也就是执行文件mybin了。

  6. 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。

  7. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。

  8. make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值