Makefile学习(1)

    技术2022-07-13  75

    前导

    gcc/g++

    GCC(GNU Compiler Collection,GNU编译器套件,GNU是一个自由的操作系统,类似于Unix)是由GNU开发的编程语言译器。g++ 是C++编译器。


    gcc 的一些参数:

    参数描述-c编译、汇编指定的源文件(也就是编译源文件),但是不进行链接-o用来指定输出文件-L为 gcc 增加一个搜索链接库的目录-l用来指定程序要链接的库-fPIC用于编译阶段,在生成目标文件时就得使用该选项,以生成位置无关的代码

    具体参数详解可参看:https://www.runoob.com/w3cnote/gcc-parameter-detail.html

    误区一:gcc只能编译C代码,g++只能编译C++代码。**

    两者都可以,但请注意:

    (1)后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是C++程序,注意,虽然C++是C的超集,但是两者对语法的要求是有区别的。C++的语法规则更加严谨一些。

    (2)编译阶段,g++会调用gcc,对于C++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。

    误区二:gcc不会定义__cplusplus宏,而g++会

    实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。

    误区三:编译只能用gcc,链接只能用g++

    严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。

    C++的编译器肯定可以编译C的代码,注意除了C++对C的语法扩充之外,编译和链接C和C++的标准库通常也不一样呢,用gcc而非g++也编译了C++的程序就证明了这一点。

    gcc与cc

    如果讨论范围在Unix和Linux之间,那么cc和gcc不是同一个东西。cc来自于Unix的c语言编译器,是 c compiler 的缩写。gcc来自Linux世界,是GNU compiler collection 的缩写,注意这是一个编译器集合,不仅仅是c或c++。

    如果讨论范围仅限于Linux,我们可以认为它们是一样的,在Linux下调用cc时,其实际上并不指向unix的cc编译器,而是指向了gcc,也就是说cc是gcc的一个链接(软连接快捷方式)。

    // 在该目录就可以看见这个符号链接 $ ls -l /usr/bin/cc

    如果我的c/c++项目是在Unix下编写的,在写makefile文件时make等会默认调用cc,当将其放到Linux下这无法make了,必须将其中的cc全部修改成gcc。所以,Linux的解决方案就是:不修改makefile,继续使用cc,这个cc是个“冒牌货”,它实际指向gcc。

    patch

    在项目中,有些模块是开源的,没有源码或者不能改动源码,想要修复、优化里面的Bug,这时就需要用到patch了。

    patch指令让用户利用设置修补文件的方式,修改,更新原始文件。倘若一次仅修改一个文件,可直接在指令列中下达指令依序执行。如果配合修补文件的方式则能一次修补大批文件,这也是Linux系统核心的升级方法之一。

    具体使用可以参看:https://www.runoob.com/linux/linux-comm-patch.html

    makefile的基本格式

    陈浩:“什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为一个专业人士,你还是要了解HTML的标识的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。”(来源于陈浩博客:http://blog.csdn.net/haoel/article/details/2886)

    可以看出来 makefile 是为了自动管理编译、链接流程而存在的,因此 makefile 的书写需要遵照一定的书写规则。

    TARGET... : PREREQUISITES... COMMAND

    TARGET:规则目标,可以是一个 object file (目标文件),也可以是一个执行文件,还可以是一个标签(label)。

    PREREQUISITES:要生成那个 target 所需要的文件或是目标,即规则依赖。

    COMMAND:也就是 make 需要执行的命令,必须以 [TAB] (一般是4个space)开始,由 shell 执行,并且可以是任意的Shell命令。

    在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。

    注:三个变量: @ , @, @^,$<代表的意义分别是:

    $@–目标文件,

    $^–所有的依赖文件,

    $<–第一个依赖文件。

    Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
    显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。

    一个简单的makefile文件来管理main.c文件的编译

    main: main.o gcc -o main main.o main.o: main.c gcc -c main.c

    make 工具的基本使用方法为:make TARGET。

    所以如果我们在Terminal输入命令: make main.o

    就会执行:

    gcc -c main.c

    相应得会生成main.o文件

    如果我们在Terminal输入命令: make main

    就会执行:

    gcc -o main main.o

    相应得会生成main文件,输入./main即可执行

    如果在执行make TARGET时候缺少依赖,则会根据依赖执行依赖生成的TARGET。 gcc -c main.c gcc -o main main.o

    即 make 先生成 makefile 中 main 的依赖文件 main.o,再链接生成 main 文件

    也可以让make自己寻找目标(即不添加任何参数直接运行)

    因为默认情况下,make 会以第一条规则作为其终极目标。

    makefile的命名规则

    make 工具会自动调用makefile 文件,但是并非文件名必须是makefile的才可以执行,GNU make 会默认的优先级查找当前文件夹的文件,查找优先级为:

    GNUmakefile> makefile> Makefile

    所以只要有高优先级的文件存在,低优先级的就不会被执行。

    然而在创建 makefile 文件时推荐以 makefile 或者 Makefile 进行命名,而不使用 GNUmakefile ,因为 GNUmakefile 只能被 GNU 的 make 工具识别到。

    makefile的时间戳

    make 命令在执行时会自动检测依赖文件的时间戳,具体规则如下:

    若依赖文件不存在或者依赖文件的时间戳比目标文件新,则执行依赖文件对应的命令。若依赖文件的时间戳比目标文件老,则忽略依赖文件对应的命令。

    makefile的依赖执行顺序

    从规则依赖左边到右边

    makefile的变量、.PHONY与-

    变量

    makefile 也可以使用变量,它类似于 C 语言中的宏定义。 变量可以直接使用vari=string的写法来定义,并以$(vari)格式来使用。 我们用变量来定义目标的依赖项,使 makefile 保持良好的扩展性。

    利用变量定义可以创建、修改、删除指定的文件序列,可以让编译工程更加自动化。

    -的使用

    因为在大部分情况下我们不会将删除一个不存在文件时所产生的错误作为真正的错误来看待,为了让 rm 命令在执行过程中即使出现了错误,makefile 也能够继续执行不退出,我们需要用到「-」符号。 而-的作用是让 make 忽略该指令的错误。

    比如:

    clean: -rm $(filelist)

    即便删除了不存在的文件产生了报错,但是makefile文件还可以继续执行

    伪目标的使用

    我们有的时候创建的依赖只是为了调用makefile文件中的目标(比如clean删除操作),而不是真的与存在的同名文件进行操作,所以就需要.PHONY来声明伪目标。

    只需要在makefile开头定义:

    .PHONY: clean

    makefile的自动推导规则

    makefile 有一套隐含的自动推导规则,具体内容如下:

    对于 xxx.o 类型的目标会默认使用命令 cc -c xxx.c -o xxx.o 进行编译。对于xxx类型的目标会默认使用命令 cc xxx.o -o xxx 进行编译。 // 如果执行 make main.o // 得到 cc -c -o main.o main.c make main cc main.o -o main //rm main.o后直接执行,编译成main文件 make main cc main.c -o main

    makefile的include关键字

    makefile 中可以使用 include 指令来包含另一个文件。 当 make 识别到 include 指令时,会暂停读入当前的 makefile 文件,并转而读入 include 指定的文件,之后再继续读取本文件的剩余内容。这个在C/Cpp、PHP、微信小程序、JS框架(webpack)中都很常见,所以通用点理解吧。

    include <filename> // filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)

    在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和可以用一个或多个空格隔开。

    include 的文件的查找路径

    当 include 指示符包含的文件不包含绝对路径,且在当前路径下也无法寻找到时,make 会按以下优先级寻找文件:

    -I 指定的目录(在make命令后的参数)/usr/gnu/include/usr/local/include/usr/include指定 makefile 的 include 路径

    至于多个文件对于一个变量作进行了定义,按照引入后的文件顺序进行相应的变更。

    makefile的环境变量问题

    如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。

    即MAKEFILES 环境变量有定义时,它的作用类似于 include。 该变量在被展开时以空格作为文件名的分隔符。

    // 加入环境变量 export MAKEFILES=./makefile_dir/c_inc // 再执行make操作 make

    也就是在环境的全局变量中(被引入的目录下的makefile)有所定义时,其他地方的makefile在放到这个环境中时可以使用该变量。最终目标执行的还是当前makefile的目标(第一个)。

    makefile的重载

    make程序在读取多个makefile文件时,包括由环境变量MAKEFILES指定、命令行指定、当前工作下的默认的以及使用指示符include指定包含的,在堆这些文件进行解析执行之前make读取的文件名将会被自动依次追加到变量MAKEFILE_LIST的定义域中。

    通过命令 make -f 指定要读取的 makefile 文件:

    make -f other_makefile

    makefile 重载另一个 makefile 的时,不允许有规则名重名。对于两个文件中同名的规则,make 后读入的规则会重写先读入的规则。

    利用重载可以在一个文件对同一个规则名但行为不同的规则,进行分别重载为makefile文件并执行相应规则。

    为了匹配到所有的未定义规则,我们需要用到通配符%。

    %: command

    未完待续~~

    可以参看陈浩博客关于makefile整理:https://blog.csdn.net/weixin_38391755/article/details/80380786

    Processed: 0.022, SQL: 9