操作系统(5)实验0——makefile的写法
之前GCC那部分我提到过,gcc啥啥啥啥傻傻的那个指令只能够编译简单的代码,如果要干大事(例如突然心血来潮写个c开头的神经网络库之类的),还是要写Makefile来编译。其实在Windows下通常用IDE,例如Visual Studio,那个所谓的“项目”就有点像Makefile。通常Windows系统下这类IDE会自动帮你配置了编译时需要的东西,而Linux环境下我们需要自己来写Makefile来实现IDE的效果,听起来会麻烦点,实际上掌握了技巧之后就那样。
这部分实验指导书里面写的东西不多,但是我觉得有必要详细拿出来讲讲,毕竟很有用。
前提与假设
这里假设使用的make是GNU的make(不同厂商的make对应的makefile写法不一样,make可以理解为根据makefile来编译链接程序的工具)。下面我们通过一个简单的例子来看makefile的具体作用、功能,以及使用方式。
简单的例子
假设当前目录下有以下文件:
当你想要编译的时候,就会使用下面的指令:
gcc -o hellomake hellomake.c hellofunc.c -I.
上面指令就是指定输出结果名字为hellomake,编译两个*.c
文件,而-I
用来高数gcc在同一个目录下寻找用到的链接文件(.h
头文件),其实这个选项还可以指定gcc去别的目录下寻找别的库,详情可以看这里。上面的-I.
不是误写,不要忽略掉.
,这是指定在当前目录搜索链接库的意思。
但是这只是在写小工程的时候才可以这么干,如果你要编译一个有20+个.c文件,链接需要30+个库的工程呢?很显然直接使用gcc相关指令来编译是不现实的,而且每次编译都要重新输入那么一长串指令,很麻烦。那么,我们这时候想到的第一个办法就是,直接将这个指令保存为一个文件,用的时候直接复制出来运行不就简单很多了?
而makefile恰好就有这个功能,你只需要将这个指令直接输入到makefile中,在直接用到的时候直接使用指令make
,工具make就会直接帮你运行makefile中的这个命令。此时makefile应该这样写:
hellomake: hellomake.c hellofunc.c hellomake.h
gcc -o hellomake hellomake.c hellofunc.c -I.
当直接使用指令make
并且没有提供任何参数的时候,make就会直接执行第一条规则,因为我们这里只有一条,所以使用指令make
就会直接执行我们想要让他执行的编译指令了。注意gcc
前面有一个tab,不能少了这个tab
。
既然提到了规则,那么就介绍下makefile中的规则写法:
在规则中,如果是第一次执行规则或者执行规则对应的prerequisites中的文件被更新了,那么在执行规则的时候才会运行规则对应的command,否则就不会执行。例如已经执行过一次hellomake规则了,如果没有更改hellomake.c、hellofunc.c、hellomake.h的话,那么再执行一次hellomake规则是不会执行下面的gcc ...
指令的;如果更改了hellomake.c,例如修改了printf里面的内容,那么再执行一次make
,就会调用规则对应的指令gcc ...
,更新编译出来的程序。
基本上到这里就可以明白Makefile的基本规则了,后面的就基本上是锦上添花的功能而已。
引入变量
假设我们突然想要换一个编译器,或者改一改编译时候的参数设置,那么我们最好定义一些变量来实现这样的功能,直接上makefile内容:
CC=gcc
CFLAGS=-I.
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
这个还是很容易懂得,可能你会觉得CFLAGS
那里有点奇怪,其实这是默认给C编译器(这里是gcc)指定额外配置用的,是make工具规定的。另外还有CXXFLAGS
、CPPFLAGS
,前者用来给C++编译器指定额外参数,后者同时给C和C++编译器指定额外参数。不过有些时候CPPFLAGS
只给C++编译器指定额外参数,所以具体情况还是要具体分析。注意-o
后面的名字,一定是.o
的,这和上面有点不一样,但是结果和上面一样。之所以放在规则里面(prerequisites部分)以及command里面,是因为这样可以让make知道在编译出hellomake之前要先编译后面的.o
文件对应的.c
部分,即能够让编译器理解它们之间的依赖关系。
但是这个makefile其实有一个问题,那就是如果修改了.h文件,那么再一次make的时候是不会编译的,因为make此时没有追踪相关的.h文件的变化。所以我们需要加上相关的规则来实现对.h文件的追踪。
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
重点在那个DEPS
和后面的%.o ...
那部分,主要讲解%.o
这部分规则。首先,我们这里定义的是一个适用于所有.o
为结尾的规则,我们将对应的.c
结尾的文件二号$(DEPS)
对应的文件放在prerequisites那部分,这样make就会去追踪这些文件的变化。最后在command部分,-c
意思是让编译器编译出.o
文件,-o $@
意思是将编译出来的文件用规则左侧的名字规则来命名(例如hellomake.o),最后的$<
指的是DEPS
中的第一个文件,CFLAGS
用来指示额外的参数。
但是还是麻烦,所以我们进一步“抽象”,把hellomake那一条规则改改
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
OBJ = hellomake.o hellofunc.o
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)
$@
代表:
左边,$^
代表右边(看键盘上的位置就知道哪个左哪个右了,这个不用记)。这样我们只需要改OBJ
就可以应付更加复杂的项目了。
引入目录
但是上面那样虽然makefile看起来还好,在项目的目录里面就显得比较杂乱,各种头文件和源代码混杂在一起显得比较没条理,所以我们通常都会将头文件集中放在一个文件夹里面,将源代码集中放在一个文件夹里面。
IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)
ODIR=obj
LDIR =../lib
LIBS=-lm
_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = hellomake.o hellofunc.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
.PHONY: clean
clean:
rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
注意,这个makefile文件时放在项目文件夹下的src文件夹里面的。大概是xx(项目名)/src这样子。LIBS
用来引入其他的库(通常安装在系统中,这里可以直接引用),例如这里用了-lm
来引用数学的库。patsubst
用来替换通配符,即变量中的%
,最终会被替换成具体的文件名。
.PHONY
用来避免文件使用make clean
的时候真的去make一个名为clean
的文件,用来确保确实跑的是定义好了的clean
规则。这里的clean
规则用来删掉编译出来的东西。其他的基本和上面的一样,所以就不多说了。
大概就这些,其实这个要深入进去的话还有很多可以说的,但是今天就记到这里。
参考
跟我一起写 Makefile(一)
A Simple Makefile Tutorial
Makefile cheatsheet速成用的cheatsheet,不过不建议一开始就看这个
CFLAGS, CCFLAGS, CXXFLAGS - what exactly do these variables control?
Makefile之patsubst
操作系统(5)实验0——makefile的写法的更多相关文章
- YII 1.0 常用CURD写法
<?php //yii1.0 curd简单写法 //查询 Yii::app()->db->createCommand($sql)->queryAll();//查询所有行数据 ...
- gtk编译之makefile的写法(之一)
在学习c语言GUI编程时想必大家都会遇见这样一个问题买就是每次编译都要敲`pkg-config --cflags --libs gtk+-2.0`这个烦恼吧 这是我们可以编写一个makefile文件这 ...
- 多个文件目录下Makefile的写法
1.前言 目前从事于linux下程序开发,涉及到多个文件,多个目录,这时候编译文件的任务量比较大,需要写Makefile.关于Makefile的详细内容可以参考网上流传非常广泛的<跟我一起写Ma ...
- 简单makefile的写法
一个项目下的文件比较多,如果单个的输入,比较费劲,所以就需要把编译过程写进一个MakeFile文件中. 下面建立5个文件,3个cxx文件,2个hxx头文件 //filename main.cxx #i ...
- FZU操作系统课程实验 实验一
实验1 [实验名称]:并发程序设计(实验1) [实验目的]:掌握在程序中创建新进程的方法, 观察并理解多道程序并发运行的现象. [实验原理]:fork():建立子进程.子进程得到父进程地址空间的一个复 ...
- 系统功能调用Windows操作系统原理实验
一.实验目的 1.熟悉操作系统的系统功能调用. 2.掌握用C语言实现系统功能调用的方法和步骤. 3.掌握利用10H号功能调用(BIOS的显示I/O功能调用)来实现对屏幕的操作与控制. 二.实验内容 1 ...
- 操作系统之实验二Step1-有序顺序表
实验二Step1-有序顺序表 专业:商业软件工程 班级:商软2班 姓名:甘佳萍 学号:201406114207 实验要求:初始化 输入数组元素个数. 输入n个数,排序输出. 存 ...
- 华为服务器操作系统EulerOS V2.0
平台: linux 类型: 虚拟机镜像 软件包: java-1.8.0 php-5.4.16 python-2.7.5 qt-4.8.5 tomcat-7.0.69 basic software eu ...
- 实验0 安装GLUT包及工程的创建与运行
下面将对Windows下在MicroSoft Visual C++2010(简称MSVC)环境下的OpenGL编程进行简单介绍. 1.安装GLUT工具包 GLUT不是OpenGL所必须的,但它会给我们 ...
随机推荐
- SQL注入--反引号
反引号是个比较特别的字符,下面记录下怎么利用 0x00 SQL注入 反引号可利用在分隔符及注释作用,不过使用范围只于表名.数据库名.字段名.起别名这些场景,下面具体说下 1)表名 payload:se ...
- 关于WPA/WPA2 4次握手
简单描述一下WPA/WPA2的4次握手中的一些关键词: WPA/WPA2使用4次握手的方式来产生所需要的密钥.四次握手通过一系列的交互,从PMK(Pairwise Master Key)生成PTK(P ...
- java高并发核心要点|系列3|锁的底层实现原理|ABA问题
继续讲CAS算法,上篇文章我们知道,CAS算法底层实现,是通过CPU的原子指令来实现. 那么这里又有一个情景: 话说,有一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且 ...
- QByteArray引发的bug
QByteArray引发的bug 在接收UDP数据的函数里,有如下代码片段 if(0x10 == data.size() && 0xCA == (unsigned char)data. ...
- python类库26[sqlite]
一 sqlite 与 python 的类型对应 二 实例 import sqlite3 def sqlite_basic(): # Connect to db conn = sqlit ...
- 源于react里面constructor()和super()的使用
es5里面没有继承的话 //构造函数 function People(name,age){ this.age = age; this.name = name } let p1 = new People ...
- 【shell】截取字符串前面文字
例如:有一个文件test.txt,里面有这些数据. meiguounix232 faguounix ribenunix zhongguounixtaobao hanguounixbaba 现在我想截取 ...
- XML to HTML
本章讲解如何把 XML 数据显示为 HTML. 在 HTML 中显示 XML 数据 在上一节中,我们讲解了如何通过 JavaScript 来解析 XML 并访问 DOM. 本例遍历一个 XML 文件 ...
- vector利用swap()函数进行内存的释放
首先,vector与deque不同,其内存占用空间只会增长,不会减小.比如你首先分配了10,000个字节,然后erase掉后面9,999个,则虽然有效元素只有一个,但是内存占用仍为10,000个.所有 ...
- luoguP1255 数楼梯 x
P1255 数楼梯 题目描述 楼梯有N阶,上楼可以一步上一阶,也可以一步上二阶. 编一个程序,计算共有多少种不同的走法. 输入输出格式 输入格式: 一个数字,楼梯数. 输出格式: 走的方式几种. 输入 ...