本读不讲理论,只讲实践。
适用对象,有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 练习就到此。