一个简单的Makefile教程

转:http://www.paeonia.me/Blog/2012/05/13/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84makefile%E6%95%99%E7%A8%8B/

写Makefile是一个非常便利的编译方法,由于以前习惯把所有的代码都集中在一个文件中,体现不出make的优势,当把源代码拆分成若干个源文件,Makefile就显得必要了。以下是一份简单的Makefile的教程,参考自A Simple Makefile Tutorial。正如原文所说,这份教程只是打算让初学者快速入门,写自己的makefile,来维护中小型的项目。

一个简单的例子,用K&R C中4.5那个例子:主程序(main.c)、函数代码(getop.c, stack.c, getch.c)、头文件(calc.h)。
一般的,我们会使用

1
gcc -o calc main.c getch.c getop.c stack.c -I.

来编译。-I.是指gcc在当前目录(.)下寻找include文件。如果不用makefile,在测试-修改-调试过程中,如果我们不想重敲那条编译指令的话,我们必须不停地在终端中按上下键来寻找最后的那条编译指令。不幸的是,这种编译方法有两个缺陷:1. 当你把编译指令丢失或者换电脑的时候,这样效率会很低;2. 当我们只修改了一个.c文件时,每一次都将必须重新编译所有的文件,这非常耗时,不划算。现在是切入主题的时候了:P

最简单的makefile写法:
Version 1

1
2
calc: main.c getch.c getop.c stack.c
    gcc -o calc main.c getch.c getop.c stack.c -I.

如果把这些语句写入一个叫Makefile或者makefile的文件,然后在终端中输入make,她将会按你在makefile中要求地编译。注意:第一行中并没有任何参数,只是在冒号(:)后列出编译中所需的文件,当第一行中的任何文件中更改时,make就知道calc需要重新编译了。现在我们已经解决了问题1,不用上下按箭头了,但是对于问题2依旧没有很好地解决。注意,非常重要:gcc前面必须有一个tab,在任何指令之前都要有一个tab,不然make就会罢工的。

让事情变得更有效率一点:
Version 2

1
2
3
4
5
CC = gcc
CFLAGS = -I.
  
calc: main.c getch.c getop.c stack.c
    $(CC) -o calc main.c getch.c getop.c stack.c $(CFLAGS)

现在我们新定义了两个常量CC和CFLAGS。这些是与make交流的特殊的常量,让make知道我们要怎么编译.c文件。CC是C编译器所使用的,CFLAGS是编译用的参数。make会先分别编译.c文件,然后生成可执行文件calc。

这种形式的makefile在小项目中非常有效,但是有一个遗憾:include文件的变动。如果我们修改了calc.h文件,make是不会重新编译.c文件的,事实上我们需要重新编译。为了解决这一问题,我们必须告诉make所有的.c文件依赖于.h文件。我们可以在makefile中增加一条规则:
Version 3

1
2
3
4
5
6
7
8
9
CC = gcc                                            
CFLAGS = -I.                                         
DEPS = calc.h                                       
                                                                                                                   
%.o: %.c $(DEPS)                                    
    $(CC) -c -o $@ $< $(CFLAGS)                          
                                                                                                                  
calc: main.o getch.o getop.o stack.o                
    $(CC) -o calc main.o getch.o getop.o stack.o $(CFLAGS)

首先宏定义DEPS,声明.c文件所依赖的.h文件。然后我们定义一条规则,为所有的.c文件生成一个.o文件。规则描述:.o文件依赖于.c文件和DEPS中声明的.h文件,为了产生.o文件,make需要使用CC中声明的编译器来编译.c文件。-c 意味着产生object文件,-o $@ 意思是编译生成的文件用上面的%.o来命名,$< 指依赖关系中的第一项(%.c)CFLAGS的定义和之前一样。

最后为了简化,我们使用特殊的宏定义 $@ 和 $^ ,分别表示冒号(:)的左右两边。为了让make中所有的规则更具通用性,在Version 4中,我们把所有的include文件作为DEPS的一部分,所有的object文件作为OBJ的一部分:
Version 4

01
02
03
04
05
06
07
08
09
10
CC = gcc
CFLAGS = -I.
DEPS = calc.h
OBJ = main.o getch.o getop.o stack.o
  
%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)
  
calc: $(OBJ)
    $(CC) -o $@ $^ $(CFLAGS)

如果我们想把.h文件放在include目录下,.c文件放在src目录下以及一些本地的库放在lib目录下,同时我们想把.o文件整理一下,避免整个目录的凌乱。在Version 5中,定义了include,lib的目录,并把object文件放到了src目录下的obj子目录,同时还包含了任何我们想要包含的库(比如说math库-lm)。这份makefile将放在src目录下。值得注意的是,在这一版本中添加了一条clean的规则,使make clean得以运行。目录结构如下:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
IDIR = ../include
CC = gcc
CFLAGS = -I$(IDIR)
  
ODIR = obj
LDIR = ../lib
  
LIBS = -lm
  
_DEPS = calc.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
  
_OBJ = main.o getch.o getop.o stack.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
  
$(ODIR)/%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)
  
calc: $(OBJ)
    gcc -o $@ $^ $(CFLAGS) $(LIBS)
  
.PHONY: clean
  
clean:
    rm-f $(ODIR)/*.o *~ core $(IDIR)/*~

其中patsubst函数包含3个参数:需要匹配的式样,用什么来替换它,需要被处理的由空格分隔的字符串。

现在我们已经有了一个不错的makefile,根据这个,我们能维护中小型的工程。当然我们能增加一些更复杂的规则;甚至创造一些规则。更多关于makefile和make请参考GNU Make Manual

这里需要再补充几点内容,这些都是开发中会遇到的问题,1常用内置函数;2 .自动化变量;3.动态库和静态库的调用:

格式: $(patsubst <pattern>,<replacement>,<text> ) 

名称:模式字符串替换函数——patsubst。
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。

   这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。

   (可以用“\”来转义,以“\%”来表示真实含义的“%”字符)
返回:函数返回被替换过后的字符串。

示例:

$(patsubst %.c,%.o, a.c b.c)

把字串“a.c b.c”符合模式[%.c]的单词替换成[%.o],返回结果是“a.o b.o”

在Makefile规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,它的用法是:

$(wildcard PATTERN...) 

在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。

例:$(wildcard *.c)”来获取工作目录下的所有的.c文件列表

notdir

使用:SRC = $(notdir wildcard)

去除所有的目录信息,SRC里的文件名列表将只有文件名。

  $@, $^, $< 
$@  表示目标文件
$^  表示所有的依赖文件
$<  表示第一个依赖文件
$?  表示比目标还要新的依赖文件列表

CFLAGS 表示用于 C 编译器的选项,
CXXFLAGS 表示用于 C++ 编译器的选项。
这两个变量实际上涵盖了编译和汇编两个步骤。

CFLAGS: 指定头文件(.h文件)的路径,如:CFLAGS=-I/usr/include -I/path/include。同样地,安装一个包时会在安装路径下建立一个include目录,当安装过程中出现问题时,试着把以前安装的包的include目录加入到该变量中来。

LDFLAGS:gcc 等编译器会用到的一些优化参数,也可以在里面指定库文件的位置。用法:LDFLAGS=-L/usr/lib -L/path/to/your/lib。每安装一个包都几乎一定的会在安装目录里建立一个lib目录。如果明明安装了某个包,而安装另一个包时,它愣是说找不到,可以抒那个包的lib路径加入的LDFALGS中试一下。

LIBS:告诉链接器要链接哪些库文件,如LIBS = -lpthread -liconv

简单地说,LDFLAGS是告诉链接器从哪里寻找库文件,而LIBS是告诉链接器要链接哪些库文件。不过使用时链接阶段这两个参数都会加上,所以你即使将这两个的值互换,也没有问题。

在讲述这个Makefile之前,还是让我们先来粗略地看一看Makefile的规则。

    target ... : prerequisites ...
            command
            ...
            ...

    target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。

    prerequisites就是,要生成那个target所需要的文件或是目标。

    command也就是make需要执行的命令。(任意的Shell命令)

这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

之前一直迷茫于冒号后面写什么,这下清楚了,冒号后面内容的作用决定是否需要重新执行command,那就意味着不写也是没关系了,但修改过就不能重新make了,除非先make clean一下。

背景:写这篇博客的原因是:最近在搞嵌入式,需要交叉编译opencv库文件,自己写Makefile,通过arm-linux-g++编译、链接、生成可执行文件,从而实现了移植的过程。平台是Toradex的Apalis TK1,三千多元,买回来我就后悔了,全是英文资料,还各种Bug,迟迟无法上手。早知如此,还不如直接买Nvidia的Jetson TK1呢。

书归正传,今天写一下Makefile文件中,动态链接库和静态链接库的生成与调用。

一、概念

动态链接库:是一种不可执行的二进制程序文件,它允许程序共享执行特殊任务所必需的代码和其他资源。Windows平台上动态链接库的后缀名是”.dll”,Linux平台上的后缀名是“.so”。Linux上动态库一般是libxxx.so;相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。

静态链接库:这类库的名字一般是libxxx.a;利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。

Makefile:利用IDE开发调试的人员可能对Makefile不大理解,其实Makefile就是完成了IDE中的编译、链接、生成等工作,并遵循shell脚本中变量定义与调用的规则。

二、编写Makefile实现编译与链接

1、准备文件

我们写一个简单的工程吧,此工程包含3个文件,分别是main.cpp,func.cpp和func.h。代码如下: 
1)main.cpp源文件:包含入口函数 int main()。该源文件中添加了“func.h”头文件,在入口函数中调用func()函数。func函数中在func.cpp中定义,在func.h中原型声明。

# main.cpp

#include "func.h"
int main()
{
    func();
    return 0;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2)func.h头文件:对void func()函数进行原型声明。

# func.h

#ifndef __FUNC_H
#define __FUNC_H

#include <iostream>

void func();

#endif
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3)func.cpp源文件:对void func()函数的定义或实现。

# func.cpp

#include "func.h"

void func()
{
    std::cout << "Hello World !" << std::endl;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2、编写Makefile文件

1)定义变量 
首先定义SOURCE,OBJS和TARGET变量,用于指代我们项目中的源文件、目标文件和可执行文件。 
2) 设置编译参数 
CC:配置编译器为g++, 
LIBS:需要调用的链接库(-l开头,去掉lib和.so。例:对 libopencv_core.so链接库的调用要写作:-lopencv_core), 
LDFLAGS:链接库的路径(-L开头), 
INCLUDE:头文件的路径。 
3)链接生成 
此步骤生成可执行文件(ELF),链接需要用到目标文件,由下一步产生 
4)编译 
此步骤生成目标文件(.o) 
5)清理 
此步骤清理可执行文件和所有的目标文件

#######################
# Makefile
#######################
# source object target
SOURCE := main.cpp func.cpp
OBJS   := main.o func.o
TARGET := main

# compile and lib parameter
CC      := g++
LIBS    :=
LDFLAGS := -L.
DEFINES :=
INCLUDE := -I.
CFLAGS  := 
CXXFLAGS:= 

# link
#$(TARGET):$(OBJS)
    $(CC) -o $@ $^

# compile
#$(OBJS):$(SOURCE)
    $(CC) -c main.cpp -o main.o
    $(CC) -c func.cpp -o func.o

# clean
clean:
    rm -fr *.o
    rm -fr $(TARGET)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

上述Makefile是将编译和链接两个步骤分开写的,我们同样可以直接从源文件生成可执行文件,自动进行编译链接等工作。 
方法:将上述Makefile中的:

# link
#$(TARGET):$(OBJS)
    $(CC) -o $@ $^

# compile
#$(OBJS):$(SOURCE)
    $(CC) -c main.cpp -o main.o
    $(CC) -c func.cpp -o func.o
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

修改为:

all:
    $(CC) -o $(TARGET) $(SOURCE)
 
 
  • 1
  • 2

其他内容,不作变化。 
6)编译、执行、清理

make
./main
make clean

 
 
  • 1
  • 2
  • 3
  • 4

三、动态链接库的生成与调用

引言:仍然利用上文中的main.cpp, func.cpp和func.h文件。下面,我们将func.cpp源文件制作成动态链接库libfunc.so,然后调用该动态库对main.cpp进行编译链接。

1、动态链接库的生成

Makefile如下:

#######################
# Makefile
#######################

# compile and lib parameter
CC      := g++
LIBS    :=
LDFLAGS :=
DEFINES :=
INCLUDE := -I.
CFLAGS  := 
CXXFLAGS:= 

# link parameter
LIB := libfunc.so

#link
$(LIB):func.o
    $(CC) -shared -o -fPIC -o $@ $^
#compile
func.o:func.cpp
    $(CC) -c -fPIC $^ -o $@ 

# clean
clean:
    rm -fr *.o
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

执行make命令之后,就可以在当前目录生成libfunc.so的动态链接库了。 
注意:动态链接库必须“以lib开头,以.so结尾”。

注:缺少 -fPIC错误

linux生成动态库时,如果遇到了relocation R_X86_64_32 against `.rodata‘ can not be used when making a shared object; recompile with -fPIC错误。 
解决方法: 
由于我的系统是AMD64位的,所以需要在编译的时候添加 -fPIC选项,重新清理编译。 
例如:

#link
$(LIB):func.o
    $(CC) -shared -o -fPIC -o $@ $^
#compile
func.o:func.cpp
    $(CC) -c -fPIC $^ -o $@ 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2、动态链接库的调用

引言:在第一部分中,我们将main.o和func.o两个目标文件进行链接,便生成了main可执行文件。如果甲方并没有提供func.cpp和func.o,只是提供了libfunc.so这个链接库,我们如何生成可执行文件呢?下文就是讲述如何利用动态库链接生成可执行文件。

Makefile如下: 
1)编译的时候需要通过INCLUDE指明头文件的路径 
2)链接的时候需要通过LDFLAGS 和 LIBS指明动态库的路径和名称。这里需要注意的是,指明动态库名称时需要“掐头去尾”,例:我们需要用到 libfunc.so库,LIBS必须定义为 -lfunc 。 
3)执行的时候,需要把libfunc.so动态库拷贝到系统环境变量包含的路径下(比如/lib或/usr/lib),这样程序在运行时才能调用到动态库。

#######################
# Makefile
#######################

# target
TARGET := main

# compile and lib parameter
CC      := g++
LDFLAGS := -L/path/to/libfunc.so
LIBS    := -lfunc
DEFINES :=
INCLUDE := -I/path/to/func.h
CFLAGS  := 
CXXFLAGS:= 

# link
$(TARGET):main.o
    $(CC) -o $@ $^ $(LDFLAGS) $(LI

#compile
main.o:main.cpp
    $(CC) $(INCLUDE) -c $^crm -fr $(TARGET)

clean:
    rm -fr *.o
    rm -fr $(TARGET)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

四、静态链接库的生成和调用

1、静态链接库的生成

引言:仍然利用上文中的main.cpp, func.cpp和func.h文件。下面,我们将func.cpp源文件制作成静态链接库func.a,然后调用该静态库对main.cpp进行编译链接。

Makefile如下: 
注意:AR:配置链接器为ar

#######################
# Makefile
#######################

# compile and lib parameter
CC      := g++
LIBS    :=
LDFLAGS := 
DEFINES :=
INCLUDE := -I.
CFLAGS  := 
CXXFLAGS:= 

# link parameter
AR  := ar
LIB := func.a

#link
$(LIB):func.o
    $(AR) -r $@ $^
#compile
func.o:func.cpp
    $(CC) -c $^ -o $@ 

# clean
clean:
    rm -fr *.o
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

执行make命令之后,就可以在当前目录生成func.a的静态链接库了。 
注意:静态链接库必须“以.a结尾”。

2、静态链接库的调用

引言:在第一部分中,我们将main.o和func.o两个目标文件进行链接,便生成了main可执行文件;第二部分,我们将main.o和libfunc.so进行链接,也可以生成main可执行文件。如果我们既没有func.o也没有func.so,该如何生成可执行文件呢?下文就是讲述如何利用静态库func.a链接生成可执行文件。

Makefile如下: 
1)编译的时候需要通过INCLUDE指明头文件的路径 
2)链接的时候需要通过LDFLAGS 和 LIBS指明静态库的路径和名称。这里不需要像动态库那样“掐头去尾”,直接写作func.a即可。 
3)执行的时候,不需要拷贝func.a至环境变量包含的路径,直接执行即可。

#######################
# Makefile
#######################

# target
TARGET := main

# compile and lib parameter
CC      := g++
LDFLAGS := -L.
LIBS    := func.a
DEFINES :=
INCLUDE := -I.
CFLAGS  := 
CXXFLAGS:= 

# link
$(TARGET):main.o
    $(CC) -o $@ $^ $(LIBS)
#compile
main.o:main.cpp
    $(CC) -c $^ -o $@

# clean
clean:
    rm -fr *.o
    rm -fr $(TARGET)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值