C/C++ 通用 Makefile

本文提供了一个用于编译和连接C/C++程序的通用Makefile,旨在简化设置过程并支持C/C++混合编程。介绍了Makefile的使用方法、参数设置及目标功能,包括如何指定可执行文件、源程序、编译选项、编译和连接过程,以及Makefile的目标(如make、makeobjs、makeclean等)。此外,提供了两个具体例子,一个HelloWorld程序和一个GTK+版HelloWorld程序,演示了Makefile的应用。

==================================================
Keywords:Makefile,make,Generic,C/C++
Author:whyglinux(whyglinuxAThotmailDOTcom)
Date:2006-03-04
==================================================

本文提供了一个用于对C/C++程序进行编译和连接以产生可执行程序的通用Makefile。

在使用Makefile之前,只需对它进行一些简单的设置即可;而且一经设置,即使以后对源程序文件有所增减一般也不再需要改 动Makefile。因此,即便是一个没有学习过Makefile书写规则的人,也可以为自己的C/C++程序快速建立一个可工作 的Makefile。

这个Makefile可以在GNUMake和GCC编译器下正常工作。但是不能保证对于其它版本的Make和编译器也能正常工作。

如果你发现了本文中的错误,或者对本文有什么感想或建议,可通过whyglinuxAThotmailDOTcom邮箱和作者联系。

此Makefile的使用方法如下:
[list=1][*]程序目录的组织
尽量将自己的源程序集中在一个目录中,并且把Makefile和源程序放在一起,这样用起来比较方便。当然,也可以将源程序分类存放在不同的目录中。

在程序目录中创建一个名为Makefile的文本文件,将后面列出的Makefile的内容复制到这个文件中。(注意:在复制的过程 中,Makfile中各命令前面的Tab字符有可能被转换成若干个空格。这种情况下需要把Makefile命令前面的这些空格替换为一 个Tab。)

将当前工作目录切换到Makefile所在的目录。目前,这个Makefile只支持在当前目录中的调用,不支持当前目录和Makefile所在的路径不是同一目录的情况。

[*]指定可执行文件
程序编译和连接成功后产生的可执行文件在Makefile中的PROGRAM变量中设定。这一项不能为空。为自己程序的可执行文件起一个有意义的名子吧。

[*]指定源程序
要编译的源程序由其所在的路径和文件的扩展名两项来确定。由于头文件是通过包含来使用的,所以在这里说的源程序不应包含头文件。

程序所在的路径在SRCDIRS中设定。如果源程序分布在不同的目录中,那么需要在SRCDIRS中一一指定,并且路径名之间用空格分隔。

在SRCEXTS中指定程序中使用的文件类型。C/C++程序的扩展名一般有比较固定的几种形 式:.c、.C、.cc、.cpp、.CPP、.c++、.cp、或者.cxx(参见mangcc)。扩展名决定了程序是C还是C++程 序:.c是C程序,其它扩展名表示C++程序。一般固定使用其中的一种扩展名即可。但是也有可能需要使用多种扩展名,这可以 在SOURCE_EXT中一一指定,各个扩展名之间用空格分隔。

虽然并不常用,但是C程序也可以被作为C++程序编译。这可以通过在Makefile中设置CC=$(CXX)和CFLAGS=$(CXXFLAGS)两项即可实现。

这个Makefile支持C、C++以及C/C++混合三种编译方式:
[list][*]如果只指定.c扩展名,那么这是一个C程序,用$(CC)表示的编译命令进行编译和连接。
[*]如果指定的是除.c之外的其它扩展名(如.cc、.cpp、.cxx等),那么这是一个C++程序,用$(CXX)进行编译和连接。
[*]如果既指定了.c,又指定了其它C++扩展名,那么这是C/C++混合程序,将用$(CC)编译其中的C程序,用$(CXX)编译其中的C++程序,最后再用$(CXX)连接程序。
[/list]
这些工作都是make根据在Makefile中提供的程序文件类型(扩展名)自动判断进行的,不需要用户干预。

[*]指定编译选项
编译选项由三部分组成:预处理选项、编译选项以及连接选项,分别由CPPFLAGS、CFLAGS与CXXFLAGS、LDFLAGS指定。

CPPFLAGS选项可参考C预处理命令cpp的说明,但是注意不能包含-M以及和-M有关的选项。如果是C/C++混合编程,也可以在这里设置C/C++的一些共同的编译选项。

CFLAGS和CXXFLAGS两个变量通常用来指定编译选项。前者仅仅用于指定C程序的编译选项,后者仅仅用于指定C++程序的 编译选项。其实也可以在两个变量中指定一些预处理选项(即一些本来应该放在CPPFLAGS中的选项),和CPPFLAGS并没有明确的界限。

连接选项在LDFLAGS中指定。如果只使用C/C++标准库,一般没有必要设置。如果使用了非标准库,应该在这里指定连接需要的选项,如库所在的路径、库名以及其它联接选项。

现在的库一般都提供了一个相应的.pc文件来记录使用库所需要的预编译选项、编译选项和连接选项等信息,通过pkg-config可以动 态提取这些选项。与由用户显式指定各个选项相比,使用pkg-config来访问库提供的选项更方便、更具通用性。在后面可以看到一个GTK+程 序的例子,其编译和连接选项的指定就是用pkg-config实现的。

[*]编译和连接
上面的各项设置好之后保存Makefile文件。执行make命令,程序就开始编译了。

命令make会根据Makefile中设置好的路径和文件类型搜索源程序文件,然后根据文件的类型调用相应的编译命令、使用相应的编译选项对程序进行编译。

编译成功之后程序的连接会自动进行。如果没有错误的话最终会产生程序的可执行文件。

注意:在对程序编译之后,会产生和源程序文件一一对应的.d文件。这是表示依赖关系的文件,通过它们make决定在源程序文件变动之后要进行哪些更新。为每一个源程序文件建立相应的.d文件这也是GNUMake推荐的方式。

[*]Makefile目标(Targets)
下面是关于这个Makefile提供的目标以及它所完成的功能:
[list][*]make
编译和连接程序。相当于makeall。
[*]makeobjs
仅仅编译程序产生.o目标文件,不进行连接(一般很少单独使用)。
[*]makeclean
删除编译产生的目标文件和依赖文件。
[*]makecleanall
删除目标文件、依赖文件以及可执行文件。
[*]makerebuild
重新编译和连接程序。相当于makeclean&&makeall。
[/list][/list]
关于这个Makefile的实现原理不准备详细解释了。如果有兴趣的话,可参考文末列出的“参考资料”。

Makefile的内容如下:

###############################################################################

#

#GenericMakefileforC/C++Program

#

#Author:whyglinux(whyglinuxAThotmailDOTcom)

#Date:2006/03/04



#Description:

#Themakefilesearchesin<SRCDIRS>directoriesforthesourcefiles

#withextensionsspecifiedin<SOURCE_EXT>,thencompilesthesources

#andfinallyproducesthe<PROGRAM>,theexecutablefile,bylinking

#theobjectives.



#Usage:

#$makecompileandlinktheprogram.

#$makeobjscompileonly(nolinking.Rarelyused).

#$makecleancleantheobjectivesanddependencies.

#$makecleanallcleantheobjectives,dependenciesandexecutable.

#$makerebuildrebuildtheprogram.Thesameasmakeclean&&makeall.

#==============================================================================



##CustomizingSection:adjustthefollowingifnecessary.

##=============================================================================



#Theexecutablefilename.

#Itmustbespecified.

#PROGRAM:=a.out#theexecutablename

PROGRAM:=



#Thedirectoriesinwhichsourcefilesreside.

#Atleastonepathshouldbespecified.

#SRCDIRS:=.#currentdirectory

SRCDIRS:=



#Thesourcefiletypes(headersexcluded).

#Atleastonetypeshouldbespecified.

#Thevalidsuffixesareamongof.c,.C,.cc,.cpp,.CPP,.c++,.cp,or.cxx.

#SRCEXTS:=.c#Cprogram

#SRCEXTS:=.cpp#C++program

#SRCEXTS:=.c.cpp#C/C++program

SRCEXTS:=



#Theflagsusedbythecpp(mancppformore).

#CPPFLAGS:=-Wall-Werror#showallwarningsandtakethemaserrors

CPPFLAGS:=



#ThecompilingflagsusedonlyforC.

#IfitisaC++program,noneedtosettheseflags.

#IfitisaCandC++mergingprogram,settheseflagsfortheCparts.

CFLAGS:=

CFLAGS+=



#ThecompilingflagsusedonlyforC++.

#IfitisaCprogram,noneedtosettheseflags.

#IfitisaCandC++mergingprogram,settheseflagsfortheC++parts.

CXXFLAGS:=

CXXFLAGS+=



#Thelibraryandthelinkoptions(CandC++common).

LDFLAGS:=

LDFLAGS+=



##ImplictSection:changethefollowingonlywhennecessary.

##=============================================================================

#TheCprogramcompiler.Uncommentittospecifyyoursexplicitly.

#CC=gcc



#TheC++programcompiler.Uncommentittospecifyyoursexplicitly.

#CXX=g++



#Uncommentthe2linestocompileCprogramsasC++ones.

#CC=$(CXX)

#CFLAGS=$(CXXFLAGS)



#Thecommandusedtodeletefile.

#RM=rm-f



##StableSection:usuallynoneedtobechanged.Butyoucanaddmore.

##=============================================================================

SHELL=/bin/sh

SOURCES=$(foreachd,$(SRCDIRS),$(wildcard$(addprefix$(d)/*,$(SRCEXTS))))

OBJS=$(foreachx,$(SRCEXTS),/

$(patsubst%$(x),%.o,$(filter%$(x),$(SOURCES))))

DEPS=$(patsubst%.o,%.d,$(OBJS))



.PHONY:allobjscleancleanallrebuild



all:$(PROGRAM)



#Rulesforcreatingthedependencyfiles(.d).

#---------------------------------------------------

%.d:%.c

@$(CC)-MM-MD$(CFLAGS)$<



%.d:%.C

@$(CC)-MM-MD$(CXXFLAGS)$<



%.d:%.cc

@$(CC)-MM-MD$(CXXFLAGS)$<



%.d:%.cpp

@$(CC)-MM-MD$(CXXFLAGS)$<



%.d:%.CPP

@$(CC)-MM-MD$(CXXFLAGS)$<



%.d:%.c++

@$(CC)-MM-MD$(CXXFLAGS)$<



%.d:%.cp

@$(CC)-MM-MD$(CXXFLAGS)$<



%.d:%.cxx

@$(CC)-MM-MD$(CXXFLAGS)$<



#Rulesforproducingtheobjects.

#---------------------------------------------------

objs:$(OBJS)



%.o:%.c

$(CC)-c$(CPPFLAGS)$(CFLAGS)$<



%.o:%.C

$(CXX)-c$(CPPFLAGS)$(CXXFLAGS)$<



%.o:%.cc

$(CXX)-c$(CPPFLAGS)$(CXXFLAGS)$<



%.o:%.cpp

$(CXX)-c$(CPPFLAGS)$(CXXFLAGS)$<



%.o:%.CPP

$(CXX)-c$(CPPFLAGS)$(CXXFLAGS)$<



%.o:%.c++

$(CXX-c$(CPPFLAGS)$(CXXFLAGS)$<



%.o:%.cp

$(CXX)-c$(CPPFLAGS)$(CXXFLAGS)$<



%.o:%.cxx

$(CXX)-c$(CPPFLAGS)$(CXXFLAGS)$<



#Rulesforproducingtheexecutable.

#----------------------------------------------

$(PROGRAM):$(OBJS)

ifeq($(strip$(SRCEXTS)),.c)#Cfile

$(CC)-o$(PROGRAM)$(OBJS)$(LDFLAGS)

else#C++file

$(CXX)-o$(PROGRAM)$(OBJS)$(LDFLAGS)

endif



-include$(DEPS)



rebuild:cleanall



clean:

@$(RM)*.o*.d



cleanall:clean

@$(RM)$(PROGRAM)$(PROGRAM).exe



###EndoftheMakefile##Suggestionsarewelcome##Allrightsreserved###

###############################################################################


下面提供两个例子来具体说明上面Makefile的用法。

[color=darkred]例一 HelloWorld程序[/color]

这个程序的功能是输出Hello,world!这样一行文字。由hello.h、hello.c、main.cxx三个文件组成。前两个文件是C程序,后一个是C++程序,因此这是一个C和C++混编程序。
/*Filename:hello.h

*Cheaderfile

*/



#ifndefHELLO_H

#defineHELLO_H



#ifdef__cplusplus

extern"C"{

#endif



voidprint_hello();



#ifdef__cplusplus

}

#endif



#endif


/*Filename:hello.c

*Csourcefile.

*/

#include"hello.h"

#include<stdio.h>



voidprint_hello()

{

puts("Hello,world!");

}


/*Filename:main.cxx

*C++sourcefile.

*/

#include"hello.h"



intmain()

{

print_hello();



return0;

}


建立一个新的目录,然后把这三个文件拷贝到目录中,也把Makefile文件拷贝到目录中。之后,对Makefile的相关项目进行如下设置:
PROGRAM:=hello#设置运行程序名

SRCDIRS:=.#源程序位于当前目录下

SRCEXTS:=.c.cxx#源程序文件有.c和.cxx两种类型

CFLAGS:=-g#为C目标程序包含GDB可用的调试信息

CXXFLAGS:=-g#为C++目标程序包含GDB可用的调试信息


由于这个简单的程序只使用了C标准库的函数(puts),所以对于CFLAGS和CXXFLAGS没有过多的要求,LDFLAGS和CPPFLAGS选项也无需设置。

经过上面的设置之后,执行make命令就可以编译程序了。如果没有错误出现的话,./hello就可以运行程序了。

如果修改了源程序的话,可以看到只有和修改有关的源文件被编译。也可以再为程序添加新的源文件,只要它们的扩展名是已经在Makefile中设置过的,那么就没有必要修改 Makefile。

[color=darkred]例二 GTK+版HelloWorld程序[/color]

这个GTK+2.0版的HelloWorld程序可以从下面的网址上得到:http://www.gtk.org/tutorial/c58.html#SEC-HELLOWORLD。当然,要编译GTK+程序,还需要你的系统上已经安装好了GTK+。

跟第一个例子一样,单独创建一个新的目录,把上面网页中提供的程序保存为main.c文件。对Makefile做如下设置:
PROGRAM:=hello#设置运行程序名

SRCDIRS:=.#源程序位于当前目录下

SRCEXTS:=.c#源程序文件只有.c一种类型

CFLAGS:=`pkg-config--cflagsgtk+-2.0`#CFLAGS

LDFLAGS:=`pkg-config--libsgtk+-2.0`#LDFLAGS


这是一个C程序,所以CXXFLAGS没有必要设置——即使被设置了也不会被使用。

编译和连接GTK+库所需要的CFLAGS和LDFLAGS由pkg-config程序自动产生。

现在就可以运行make命令编译、./hello执行这个GTK+程序了。

参考资料:
[list=1][*]Multi-fileprojectsandtheGNUMakeutility
Author:GeorgeFoot
http://www.elitecoders.de/mags/cscene/CS2/CS2-10.html

[*]GNUMakeManual
http://www.gnu.org/software/make/manual/
[/list]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值