1、编写一个简单Makefile模板
一、Makefile简介
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。linux内核的编译同样也遵循这些规则,具体说明可见kernel/Documentation/kbuild/makefiles.txt
二、简单编写一个Makefile模板
当编译少量的源文件时,可以用以下两种方法编译
1、直接用一条命令编译
gcc -o test 1.c 2.c
编译过程说明:
执行gcc -o test 1.c 2.c时,
对于1.c:预编译、编译、汇编、链接
对于1.c:预编译、编译、汇编、链接
优点:命令简单,易操作
缺点:如果文件很多,即使修改了一个文件,但是所有的文件都要重新"预处理、编译、汇编",效率很低
2、通过Makefile建立编译规则
一个的Makefile文件包含一系列的规则(想了解更多规则可参考《GNU make手册》),其格式如下:
目标 : 依赖1 依赖2 ... 依赖n
命令
注意:命令的前边必须是一个Tab
命令执行的条件:
(1)"依赖"文件比"目标"的时间新
(2)没有"目标"这个文件
三、代码实践
功能说明:当任何一个文件发生改变时,其所有依赖文件和自己都会被重新编译,而其他与此没有关联的文件则不会被编译
源文件如下:
<1>文件1.c
#include <stdio.h>
#include "1.h"
int main(char argc, char *argv[])
{
printf("This is 1.c\n");
func();
printf("AA = %d\n", AA); return ;
}
<2>文件2.c
#include <stdio.h>
void func(void)
{
printf("This is 2.c\n");
}
<3>文件1.h
#define AA 2
1、第1个Makefile
target:.c .c .h
gcc -o target .c .c
解析:
第一次编译(没有目标生成)或依赖发生变化(依赖比目标新)就执行下面的命令这样的缺点就是,只要其中的一个依赖发生变化,所有的文件都要重新被编译一遍,这样效率极低
2、第2个Makefile
target:.o .o
gcc -o target .o .o
#-c表示只编译不链接(只进行预编译、编译、汇编)
.o : .c
gcc -c -o .o .c .o : .c
gcc -c -o .o .c
解析:
解析:当执行make命令时,想要生成第一个目标target,而target依赖于1.o、2.o,但是当前目录下并没有1.o、2.o,它会用1.o往下查找到1.o依赖于1.c,同时判断(1.c是否比1.o新或1.o是否存在)是否执行gcc -c -o .1.o 1.c,我们这里满足第二个条件所以此命令会被执行,分析1.o也是如此
3、第3个Makefile(效果同第2个Makefile)
target:.o .o
gcc -o target .o .o %.o : %.c
gcc -c -o $@ $<
解析:
%.o : %.c,
gcc -c -o $@ $<
等同于
1.o : 1.c
gcc -c -o 1.o 1.c
2.o : 2.c
gcc -c -o 2.o 2.c
%为通配符,%.o,表示所有的.o文件%.c,表示所有的.c文件,
$@表示目标(对应%.o),$<表示第一个依赖(对应%.c)
测试:
(1)第一次编译,则所有的文件都会被编译
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# ls
1.c 1.h 2.c Makefile
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
gcc -c -o 1.o 1.c
gcc -c -o 2.o 2.c
gcc -o target 1.o 2.o
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# ls
1.c 1.h 1.o 2.c 2.o Makefile target
(2)只修改1.c,则所有依赖1.c的文件都会重新被编译,而2.c不会重新被编译
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# vi 1.c
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
gcc -c -o 1.o 1.c
gcc -o target 1.o 2.o
(3)只修改2.c,则所有依赖2.c的文件都会重新被编译,而1.c不会重新被编译
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# vi 2.c
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
gcc -c -o 2.o 2.c
gcc -o target 1.o 2.o
(4)但是修改1.h就不行了,不能检测到1.h的更新,第4个Makefile中继续修改
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# vi 1.h
root@ubuntu:/mnt/hgfs/winshare/mak/2_makefile# make
make: `target' is up to date.
4、第4个Makefile
target:.o .o
gcc -o target .o .o
#添加此行,当修改1.h时,所有依赖1.h的文件都会重新编译
.o : .c .h %.o : %.c
gcc -c -o $@ $<
解析:
(1)1.o依赖于1.c,同样也依赖于1.h,故添加1.o : 1.c 1.h
(2)make时就会检测1.c所包含的1.h是否发生改变,若发生改变,则重新编译
(3)但存在一个问题,当有上万个.c文件时,要查看它所包含的.h文件是否发生改变,这样操作就不行了,那么是否有什么自动的规则,来生成依赖文件,第5个Makefile中继续修改
测试:
root@ubuntu:/mnt/hgfs/winshare/mak/4_makefile# make
gcc -c -o 1.o 1.c
gcc -o target 1.o 2.o
5、第5个Makefile
利用-Wp,-MD,1.d自动生成依赖存到1.d中,1.o的所有依赖如下
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# gcc -c -o 1.o 1.c -Wp,-MD,1.d
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# ls
1.c 1.d 1.h 1.o 2.c Makefile
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# vi 1.d
1.o: 1.c /usr/include/stdc-predef.h /usr/include/stdio.h \
/usr/include/features.h /usr/include/i386-linux-gnu/sys/cdefs.h \
/usr/include/i386-linux-gnu/bits/wordsize.h \
/usr/include/i386-linux-gnu/gnu/stubs.h \
/usr/include/i386-linux-gnu/gnu/stubs-32.h \
/usr/lib/gcc/i686-linux-gnu/4.8/include/stddef.h \
/usr/include/i386-linux-gnu/bits/types.h \
/usr/include/i386-linux-gnu/bits/typesizes.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/i686-linux-gnu/4.8/include/stdarg.h \
/usr/include/i386-linux-gnu/bits/stdio_lim.h \
/usr/include/i386-linux-gnu/bits/sys_errlist.h 1.h
target:.o .o
gcc -o target .o .o %.o : %.c
gcc -c -Wp,-MD,$@.d -o $@ $< clean:
rm *.o target distclean:
rm *.o target *.d
(1)-Wp,-MD,$@.d表示编译时自动生成1.o.d、2.o.d,所有关于1.o、2.o的依赖
都保存在1.o.d、2.o.d
(2)clean,distclean表示伪目标,make clean 时,则执行rm *.o target,
make distclean也如此
6、第6个Makefile
objs := .o .o target:$(objs)
gcc -o target $^
$(warning $(objs))
# ..o.d ..o.d
dep_files := $(foreach f,$(objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
$(warning $(dep_files))
ifneq ($(dep_files),)
include $(dep_files)
endif %.o : %.c
gcc -c -Wp,-MD,.$@.d -o $@ $< clean:
rm *.o target distclean:
rm -f *.o target .*.d
解析:
(1)定义一个立即变量objs来表示target的依赖
(2)$(foreach f,$(objs),.$(f).d)的分析:
foreach 含义是每一个,是一个函数,类似于for循环
f 代表$(objs)中的一个成员变量1.o或2.o,
.$(f).d 把$(objs)中的每个成员都加上前缀.和后缀.d,如.1.o.d和.2.o.d
(3)$(wildcard $(dep_files))表示取出$(dep_files)中的成员变量
(4)ifneq ($(dep_files),)判断$(dep_files)是否非空,非空条件成立
(5)至此,所有.h类型的依赖被包含进去
测试:
单独修改1.h编译成功,修改AA的值为3
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# vi 1.h
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# make
.1.o.d .2.o.d
gcc -c -Wp,-MD,.1.o.d -o 1.o 1.c
gcc -o target 1.o 2.o
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# la
1.c 1.h 1.o .1.o.d 2.c 2.o .2.o.d Makefile target
root@ubuntu:/mnt/hgfs/winshare/mak/6_makefile# ./target
This is 1.c
This is 2.c
AA = 3
1、编写一个简单Makefile模板的更多相关文章
- 编写一个简单的内核驱动模块时报错 “/lib/modules/3.13.0-32-generic/bulid: 没有那个文件或目录。 停止。”
编写一个简单的内核驱动模块 static int hello_init() { printk(“hello,I am in kernel now\n”); ; } void addfunc(int a ...
- 使用CEF(二)— 基于VS2019编写一个简单CEF样例
使用CEF(二)- 基于VS2019编写一个简单CEF样例 在这一节中,本人将会在Windows下使用VS2019创建一个空白的C++Windows Desktop Application项目,逐步进 ...
- 编写一个简单的C++程序
编写一个简单的C++程序 每个C++程序都包含一个或多个函数(function),其中一个必须命名为main.操作系统通过调用main来运行C++程序.下面是一个非常简单的main函数,它什么也不干, ...
- 使用Java编写一个简单的Web的监控系统cpu利用率,cpu温度,总内存大小
原文:http://www.jb51.net/article/75002.htm 这篇文章主要介绍了使用Java编写一个简单的Web的监控系统的例子,并且将重要信息转为XML通过网页前端显示,非常之实 ...
- 一个简单的模板引(han)擎(shu)
自制一个简单的模板引(han)擎(shu) 原理 说大了 实际上是模板函数 原理呢就是简单的字符串替换 第一版 var data = { username: 'Muhha' } str = '< ...
- 编写一个简单的Web Server
编写一个简单的Web Server其实是轻而易举的.如果我们只是想托管一些HTML页面,我们可以这么实现: 在VS2013中创建一个C# 控制台程序 编写一个字符串扩展方法类,主要用于在URL中截取文 ...
- javascript编写一个简单的编译器(理解抽象语法树AST)
javascript编写一个简单的编译器(理解抽象语法树AST) 编译器 是一种接收一段代码,然后把它转成一些其他一种机制.我们现在来做一个在一张纸上画出一条线,那么我们画出一条线需要定义的条件如下: ...
- Java入门篇(一)——如何编写一个简单的Java程序
最近准备花费很长一段时间写一些关于Java的从入门到进阶再到项目开发的教程,希望对初学Java的朋友们有所帮助,更快的融入Java的学习之中. 主要内容包括JavaSE.JavaEE的基础知识以及如何 ...
- 用 Go 编写一个简单的 WebSocket 推送服务
用 Go 编写一个简单的 WebSocket 推送服务 本文中代码可以在 github.com/alfred-zhong/wserver 获取. 背景 最近拿到需求要在网页上展示报警信息.以往报警信息 ...
随机推荐
- Debian 系linux切换登录管理器(display manager)
在控制台中sudo dpkg-reconfigure <你的dm包名>即可dm选择列表,选择自己需要的dm 例如ubutu18默认使用gdm3,则输入命令: sudo dpkg-recon ...
- tomcat 内存溢出处理方案
找到tomcat7w.exe 在java 页 java options 最后添加 -XX:PermSize=256m-XX:MaxPermSize=512m
- 如何将本地的文件上传到你的github仓库中(首次流程)
1.(先进入项目文件夹,右键项目文件夹,选择git Bash)通过命令 git init 把这个目录变成git可以管理的仓库 git init 2.把文件添加到版本库中,使用命令 git add . ...
- Sublime Text 3(3207)安装
Sublime Text 3207 下载 官网地址: Sublime Text 下载需要的类型 安装插件 安装插件管理器: 打开Sublime,点击Tools => Install Packag ...
- rest参数与扩展运算符
rest参数与扩展运算符 rest参数 当遇上这样一种需求:对于输入的参数,求和返回,但传入的参数个数并不确定. // 在es5中,通常是使用函数自身的arguments对象实现的 function ...
- 深入理解java虚拟机《一》
一.java发展史 1995.5.23 Oak语言改名为java,sun正式发布java 1.0版本 1996.1.23 JDK 1.0发布,java语言第一个正式版本运行环境 主要包括:java虚拟 ...
- C语言 进制转换
这个程序仅仅是由十进制转换为其他进制的过程,其转换的规则如下图所示. 我使用的思路:首先在除基的过程中用一个数组保存余数,然后在输出进制转换结果的时候倒序输出,并且在输出前判断余数是否大于10,如果大 ...
- SharePoint 2013 新特性 (三) 破改式 —— 设计管理器的使用 [2.HTML变身模板页]
假设你跟我一样,看到了一个非常漂亮的页面,想把这种风格放到SharePoint里,咋办呢,那肯定得自定义个模板页了,好点是SharePoint Designer搞定,差点就得用Visual Studi ...
- google Kickstart Round F 2017 四道题题解
Problem A. Kicksort 题意抽象一下为: 对于一个每次都从数列正中间取划分数的快速排序,给定一个1-n的排列,问快排的复杂度对于这个排列是否会退化为最坏复杂度. 数据范围: 测试组数1 ...
- 在SQL Server 2008上安装ArcSDE 10.1并实现远程连接
先安装SQL Server 2008 R2 X64(SP2),创建数据库实例,安装客户端. 再安装ArcSDE 10.1,ArcGIS Desktop 10.1,一切顺利. 由于Desktop是32位 ...