Makefile 的编写过程(实践)

本读不讲理论,只讲实践。

适用对象,有GCC基础,对GCC编译有了解的人群。

 

1、先了解程序从无到有的过程,即编译到执行。

(图片的引用来自互联网)

下面用实例来演示整个过程。

以一个.c文件来演示吧,这里只谈讨编译到运行的过程,不讨论编码的复杂度。

add.c

#include "stdio.h"

int add(int x,int y){
     return x+y;
}

void main()
{
      int c;
      c = add(20,30);
      printf("%d",c);
}


 

(1)先将源文件进行预编译产生.i文件

使用GCC的-E参数。

gcc -E -c add.c -o add.i

可以使用cat add.i来查看.i中的内容,内容太多我就不贴了。

cat -n add.i

(2)通过.i文件来产生汇编文件.s

gcc -S add.i


(3)通过.s文件生成.o文件(目标连接文件,即中间文件)

gcc  -c add.s

(4)通过.o文件连接成最终的执行文件。

gcc add.o -o add

(5)最后执行生成的可执行文件。

./add

 

make file 的编写

理论知识请见:http://blog.csdn.net/fengsh998/article/details/8161217

新建一个文件夹,我这里以建好的文件夹为:f:\so\makefile

在其中放入文件:utils.h ,utils.c ,controller.h,controller.cpp,business.h,business.cpp,config.h,config.cpp,makefiledemo.cpp

utils.h

#ifndef UTILS_H
#define UTILS_H


#ifdef __cplusplus
#define EXPORT extern "C" __declspec(dllexport)
#else
#define EXPORT __declspec(dllexport)
#endif

EXPORT void toSetData();
EXPORT void toGetData();

#endif


util.c

#include "utils.h"
#include "stdio.h"

EXPORT void toSetData()
{
#ifdef USE_ME
	printf("C call toSetData function for use me.\n");
#else
	printf("C call toSetData function.\n");
#endif
}

EXPORT void toGetData()
{
	printf("C call toGetData function.\n");
}


controller.h

#ifndef CONTROLLER_H
#define CONTROLLER_H

#include "config.h"
#include "string"

class CController
{
public:
	CController(void);
	~CController(void);

	void viewConfig();
	void setConfig(const std::string& cfg);
};

#endif

 

controller.cpp

#include "controller.h"

CController::CController(void)
{
}

CController::~CController(void)
{
}

void CController::setConfig(const std::string& cfg)
{
	CConfig::getInstance()->writeConfig(cfg);
}

void CController::viewConfig()
{
	CConfig::getInstance()->readConfig();
}

 

business.h

#ifndef BUSINESS_H
#define BUSINESS_H

#include "config.h"

class CBusiness
{
public:
	CBusiness(void);
	~CBusiness(void);

	void toReadConfig();
	void toWriteConfig(const std::string& cfg);
};

#endif

 

business.cpp

#include "business.h"

CBusiness::CBusiness(void)
{
}

CBusiness::~CBusiness(void)
{
}

void CBusiness::toReadConfig()
{
	CConfig::getInstance()->readConfig();
}

void CBusiness::toWriteConfig(const std::string& cfg)
{
	CConfig::getInstance()->writeConfig(cfg);
}


config.h

/****************************************************
		file :	config.h
   
     author : fengsh
	 instruction:
		用于系统配置项数据存取,使用单例
			
****************************************************/
#ifndef CCONFIG_H
#define CCONFIG_H

#include "string"

class CConfig
{
public:
	CConfig(void);
	~CConfig(void);

	static CConfig* getInstance();

	void readConfig();
	void writeConfig(const std::string& cfg);

private:
	static CConfig* m_config;
	std::string m_cfg;
};

#endif

 

config.cpp

#include "config.h"
#include <iostream>

CConfig* CConfig::m_config = NULL;

CConfig::CConfig(void)
{
}

CConfig::~CConfig(void)
{
}

CConfig* CConfig::getInstance()
{
	if (!m_config)
	{
		m_config = new CConfig();
	}
	return m_config;
}

void CConfig::readConfig()
{
	std::cout<<"read config value = "<<m_cfg<<" success."<<std::endl;
}

void CConfig::writeConfig(const std::string& cfg)
{
	m_cfg = cfg;
	std::cout<<"write config value = "<<m_cfg<<" success."<<std::endl;
}


 

makefiledemo.cpp

// makefiledemo.cpp : 定义控制台应用程序的入口点。
//

#include "stdlib.h"
#include "business.h"
#include "controller.h"
#include "utils.h"

int main(int argc, char* argv[])
{
	CBusiness *bus = new CBusiness();
	bus->toWriteConfig("business");
	bus->toReadConfig();

	CController *ctl = new CController();
	ctl->setConfig("controller");
	ctl->viewConfig();

	toGetData();
	toSetData();

	return 0;
}


以上文件准备好后,就可以使用g++ 命令进行编译产生.o文件。

g++ -Wall -c config.cpp business.cpp controller.cpp makefiledemo.cpp

gcc -Wall -c utils.c

以上可以产生.o文件。

好,MAKEFILE无非就是人工转自动。即让命令逐个自动执行,是不是有点类似于批处理啊。

上面命令的目的在于产生.o文件,而产生.o文件依赖于.cpp/.c文件。

========写为makefile文件========

config.o:config.cpp config.h
 g++ -Wall -c config.cpp
controller.o:controller.cpp controller.h
 g++ -Wall -c controller.cpp
business.o:business.cpp business.h
 g++ -Wall -c business.cpp
makefiledemo.o:makefiledemo.cpp
 g++ -Wall -c makefiledemo.cpp
utils.o:utils.c utils.h
 gcc -Wall -c utils.c

=============================

但是在执行make时,只执行了第一个命令,后面不走了。因此想到所后面的目标集结起来放在第一句来写。即,将上文件的最前面加上一句:

demo:config.o controller.o business.o makefiledemo.o utils.o

这样进行make就会把上面的.cpp/.c文件全部编译成.o文件。

上面可以全部编了,但后续维护起来却十分麻烦,如果加文件时,就会显得很长,很不好观看。

改版后:

========写为makefile文件========

demo:utils.o
 g++ -Wall -c config.cpp controller.cpp business.cpp makefiledemo.cpp
utils.o:utils.c
 gcc -Wall -c utils.c

=============================

同样可以完成编译。但编写的代码少了点。g++ -Wall -c config.cpp controller.cpp business.cpp makefiledemo.cpp这句应该很熟悉吧,我们手动在GCC中就是这样产生.o的。只不过这里把命令放在makefile中自动执行而已。

在上面中看到-Wall -c 如果有多个执行的时候,还是要写多次,如果编译项有变化改为-Wall -c -O 哪么也要相应的改动好多行。因此想到使用变量。

改版:

========写为makefile文件========

FILES = config.cpp controller.cpp business.cpp makefiledemo.cpp
CFLAG = -Wall -c
demo:utils.o
 g++ $(CFLAG) $(FILES)
utils.o:utils.c
 gcc $(CFLAG) utils.c

=============================

这个已经精间了些了,但对于FILES中的文件添加还是要不停的来改这个makefile文件,哪有没有可经遍历目录中的.cpp/.c文件呢?有的。

改版:

========写为makefile文件========

CFLAG = -Wall -c
CPP_FILES = $(wildcard *.cpp)
C_FILES = $(wildcard *.c)
demo:utils.o viewcpp viewc
 g++ $(CFLAG) $(CPP_FILES)
utils.o:$(C_FILES)
 gcc $(CFLAG) $(C_FILES)
viewcpp:
 echo $(CPP_FILES)
viewc:
 echo $(C_FILES)

=============================

上面的红色部分是用来输出CPP_FILES和C_FILES列表,看看是否获取得正确。见效果图:

上面使用了wildcard函数来查找以*.c和*.cpp 结尾的文件生成文件列表。如果不用区分是c还是cpp文件可以直接使用$(wildcard  *.c   *.cpp)来搞掂。

到这里已经简了好多字符了,但还有一点不爽,上面的有使用GCC和G++分开编译的,特别是在某些项目即有C又有C++的情况下,这样写还是要区分是c还是cpp的来自进行写相应的命令。可不可以更自动化些,让机器在编译的时候如果碰到c的使用GCC如果碰到CPP的使用G++进行。

改版:

========写为makefile文件========

CFLAG = -Wall -c
CPP_FILES = $(wildcard *.cpp)
C_FILES = $(wildcard *.c)
GCC = gcc
GXX = g++
%.o:%.c
 $(GCC) $(CFLAG) $< -o $@
demo:utils.o
 $(GXX) $(CFLAG) $(CPP_FILES)

=============================

可以看到Demo的依赖是utils.o,而%.o:%.c是指所有.o结尾的集合的目标文件中,其依赖关系为.c结尾的文件。

因此程序在执行make时,执行到demo,发现demo依赖utils.o 然后去查.o文件,发现本地没有.o文件,因此通过.o来查找相应的.c 和.cpp ,但make很聪明,知道utils.o依赖的是utils.c文件,这个正好符合%.o:%.c的条件,于是优先执行$(GCC) $(CFLAG) $< -o $@这句,翻译回来为:gcc  -Wall -c utils.c -o utils.o ;因为$<为所有依赖,正因为只有一个utils.o所以只有一个.c的依赖。

第一次make:(在本地没有utils.o文件的情况下)

再次执行make,可以发现已经不执行$(GCC) $(CFLAG) $< -o $@这句了。因为本地已有utils.o文件了。

 

再改版一下,让.c的和.cpp的自动匹配使用gcc 和g++进行编译

========写为makefile文件========

CFLAG = -Wall -c
LIST_O = config.o business.o controller.o makefiledemo.o utils.o
GCC = gcc
GXX = g++
%.o:%.c
 $(GCC) $(CFLAG) $< -o $@
%.o:%.cpp
 $(GXX) $(CFLAG) $< -o $@
demo:$(LIST_O)
 g++ $(LIST_O) -o $@

=============================

 g++ $(LIST_O) -o $@ 这句只是用来让程序运行起来好走下面这段化码。 

$(GCC) $(CFLAG) $< -o $@
%.o:%.cpp
 $(GXX) $(CFLAG) $< -o $@
到此可以看到,我们只需要构建LIST_O变量为动态就OK了。好,哪使用wildcar来处理。

改造为:

========写为makefile文件========

CFLAG = -Wall -c
SRC = $(wildcard *.c *.cpp)
LIST_O = $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SRC)))
GCC = gcc
GXX = g++
%.o:%.c
 $(GCC) $(CFLAG) $< -o $@
%.o:%.cpp
 $(GXX) $(CFLAG) $< -o $@
demo:$(LIST_O)
 $(GXX) $(LIST_O) -o $@

=============================

 

 

目录的使用:

通常为了便于管理,通常把文件归类。这里把.h文件放在include文件平,把.c和.cpp放在src/c_code ,src/cpp_code下。把输出的连接中间文件(.o)放在obj文件夹,最终可执行文件放在bin文件,库文件为lib文件夹。

 

========写为makefile文件========

 

#define compiler var.
GCC = gcc
GXX = g++

#define compiler switch.
CFLAG = -Wall -c

#define header of files dir.
INCLUDE = ./include

#define compiler source code dir.
SRC_DIR = ./src
SRC = $(wildcard $(SRC_DIR)/c_code/*.c $(SRC_DIR)/cpp_code/*.cpp)

#define output dir for object files.
OBJOUTPUT = ./obj/

#define output dir for bin
BINOUTPUT = ./bin/

OBJS = $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SRC)))

OBJS_DIR = $(addprefix $(OBJOUTPUT),$(notdir $(OBJS)))

%.o:%.c
 $(GCC) $(CFLAG) $< -I$(INCLUDE) -o $@
 mv $@ $(OBJOUTPUT)
%.o:%.cpp
 $(GXX) $(CFLAG) $< -I$(INCLUDE) -o $@
 mv $@ $(OBJOUTPUT)
demo:$(OBJS)$
 $(GXX) $(OBJS_DIR) -o $(BINOUTPUT)$@
 
clean:
 rm -f $(OBJOUTPUT)*.o

=============================

 SRC 变量,是整个C/CPP源文件列表的集合,OBJS 对应的是将.c和.cpp文件相应当转为.o文件(包括路径),后面要指定OBJ的输出路径,所以必须将源文件的中径去除,去除后存在OBJS_DIR里。上面用到了mv命令,就是将生据的.o文件移动到指定目录下。

执行步骤,make后,执行target = demo,但因为demo 依赖于变量OBJS,这里的OBJS正好为源文件所在的路径,是整个.o文件的集合,因引将.o文件为目标进行展开查找相应的依赖.c/.cpp文件,所以先执行上面的语句。等完成后,再执行demo下面的命令。这里加上了一个clean目标,主要是为了方便删除中间变量。在执行make后可以再执行make clean来删除。

 

之前有一个utils.c文件中用到了预编译指令,USE_ME的宏,这个可以在编译时使用-D来进行指定,是不是有点像用来编译不同的版本呢?如命令

gcc -Wall -c -DUSM_ME utils.c

OK,makefile 练习就到此。

 

 

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

边缘998

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值