先来想像一个案例,假设我的可执行文件里面包含了四个源代码文件,分别是 main.c haha.c sin_value.c cos_value.c 这四个文件,这四个文件的目的是:

    • main.c :主要的目的是让使用者输入角度数据与调用其他三支副程序;
    • haha.c :输出一堆有的没有的讯息而已;
    • sin_value.c :计算使用者输入的角度(360) sin 数值;
    • cos_value.c :计算使用者输入的角度(360) cos 数值。
      这四个文件你可以到 来下载。由于这四个文件里面包含了相关性,并且还用到数学函数在里面,所以如果你想要让这个程序可以跑, 那么就需要这样编译:

    编译的过程需要进行好多动作啊!而且如果要重新编译,则上述的流程得要重新来一遍,光是找出这些指令就够烦人的了! 如果可以的话,能不能一个步骤就给他完成上面所有的动作呢?那就利用 make 这个工具吧! 先试看看在这个目录下创建一个名为 makefile 的文件,内容如下:

    1. [root@study ~]# vim makefile
    2. main: main.o haha.o sin_value.o cos_value.o
    3. gcc -o main main.o haha.o sin_value.o cos_value.o -lm
    4. # 注意:第二行的 gcc 之前是 <tab> 按键产生的空格喔!
    5. # 2\. 尝试使用 makefile 制订的规则进行编译的行为:
    6. [root@study ~]# rm -f main *.o <==先将之前的目标文件去除
    7. [root@study ~]# make
    8. cc -c -o main.o main.c
    9. cc -c -o haha.o haha.c
    10. cc -c -o sin_value.o sin_value.c
    11. cc -c -o cos_value.o cos_value.c
    12. gcc -o main main.o haha.o sin_value.o cos_value.o -lm
    13. # 此时 make 会去读取 makefile 的内容,并根据内容直接去给他编译相关的文件啰!
    14. # 3\. 在不删除任何文件的情况下,重新执行一次编译的动作:
    15. # 看到了吧!是否很方便呢!只会进行更新 (update) 的动作而已。

    或许你会说:“如果我创建一个 shell script 来将上面的所有动作都集结在一起,不是具有同样的效果吗?”呵呵! 效果当然不一样,以上面的测试为例,我们仅写出 main 需要的目标文件,结果 make 会主动的去判断每个目标文件相关的源代码文件,并直接予以编译,最后再直接进行链接的动作! 真的是很方便啊!此外,如果我们更动过某些源代码文件,则 make 也可以主动的判断哪一个源代码与相关的目标文件文件有更新过, 并仅更新该文件,如此一来,将可大大的节省很多编译的时间呢!要知道,某些程序在进行编译的行为时,会消耗很多的 CPU 资源呢!所以说, make 有这些好处:

    • 简化编译时所需要下达的指令;
    • 若在编译完成之后,修改了某个源代码文件,则 make 仅会针对被修改了的文件进行编译,其他的 object file 不会被更动;
    • 最后可以依照相依性来更新 (update) 可执行文件。
      既然 make 有这么多的优点,那么我们当然就得好好的了解一下 make 这个令人关心的家伙啦!而 make 里面最需要注意的大概就是那个规则文件,也就是 makefile 这个文件的语法啦!所以下面我们就针对 makefile 的语法来加以介绍啰。

    21.3.2 makefile 的基本语法与变量

    make 的语法可是相当的多而复杂的,有兴趣的话可以到 GNU [1] 去查阅相关的说明,鸟哥这里仅列出一些基本的规则,重点在于让读者们未来在接触源代码时,不会太紧张啊! 好了,基本的 makefile 规则是这样的:

    1. 标的(target): 目标文件1 目标文件2
    2. <tab> gcc -o 欲创建的可执行文件 目标文件1 目标文件2
    • 在 makefile 当中的 # 代表注解;
    • 需要在命令行 (例如 gcc 这个编译器指令) 的第一个字符;
    • 标的 (target) 与相依文件(就是目标文件)之间需以“:”隔开。
      同样的,我们以刚刚上一个小节的范例进一步说明,如果我想要有两个以上的执行动作时, 例如下达一个指令就直接清除掉所有的目标文件与可执行文件,该如何制作呢?

    如此一来,我们的 makefile 里面就具有至少两个标的,分别是 main 与 clean ,如果我们想要创建 main 的话,输入“make main”,如果想要清除有的没的,输入“make clean”即可啊!而如果想要先清除目标文件再编译 main 这个程序的话,就可以这样输入:“make clean main”,如下所示:

    1. [root@study ~]# make clean main
    2. rm -rf main main.o haha.o sin_value.o cos_value.o
    3. cc -c -o main.o main.c
    4. cc -c -o haha.o haha.c
    5. cc -c -o sin_value.o sin_value.c
    6. cc -c -o cos_value.o cos_value.c
    7. gcc -o main main.o haha.o sin_value.o cos_value.o -lm

    这样就很清楚了吧!但是,你是否会觉得,咦! makefile 里面怎么重复的数据这么多啊!没错!所以我们可以再借由 shell script 那时学到的“变量”来更简化 makefile 喔:

    1. [root@study ~]# vi makefile
    2. LIBS = -lm
    3. OBJS = main.o haha.o sin_value.o cos_value.o
    4. main: ${OBJS}
    5. gcc -o main ${OBJS} ${LIBS}
    6. rm -f main ${OBJS}

    与 的语法有点不太相同,变量的基本语法为:

    • 变量与变量内容以“=”隔开,同时两边可以具有空格;
    • 变量与变量内容在“=”两边不能具有“:”;
    • 在习惯上,变量最好是以“大写字母”为主;
    • 运用变量时,以 ${变量} 或 $(变量) 使用;
    • 在该 shell 的环境变量是可以被套用的,例如提到的 CFLAGS 这个变量!
    • 在命令行界面也可以给予变量。
      由于 gcc 在进行编译的行为时,会主动的去读取 CFLAGS 这个环境变量,所以,你可以直接在 shell 定义出这个环境变量,也可以在 makefile 文件里面去定义,更可以在命令行当中给予这个咚咚呢!例如:

    也可以这样:

    1. [root@study ~]# vi makefile
    2. LIBS = -lm
    3. OBJS = main.o haha.o sin_value.o cos_value.o
    4. CFLAGS = -Wall
    5. main: ${OBJS}
    6. gcc -o main ${OBJS} ${LIBS}
    7. clean:
    8. rm -f main ${OBJS}
    • make 命令行后面加上的环境变量为优先;
    • makefile 里面指定的环境变量第二;
    • shell 原本具有的环境变量第三。
      此外,还有一些特殊的变量需要了解的喔:

    • $@:代表目前的标的(target)
      所以我也可以将 makefile 改成:

    1. [root@study ~]# vi makefile
    2. LIBS = -lm
    3. OBJS = main.o haha.o sin_value.o cos_value.o
    4. CFLAGS = -Wall
    5. main: ${OBJS}
    6. gcc -o $@ ${OBJS} ${LIBS} <==那个 $@ 就是 main
    7. clean:

    这样是否稍微了解了 makefile (也可能是 Makefile) 的基本语法?这对于你未来自行修改源代码的编译规则时,是很有帮助的喔!^_^!