0
点赞
收藏
分享

微信扫一扫

C++进阶3:Makefile入门

东林梁 2022-04-19 阅读 30
c++

C++进阶3:Makefile入门

0、作用

Makefile文件告诉Make怎样编译和连接成一个程序。

1、Makefiile基本语法与执行

1.1 示例

编译一个单文件HelloWorld.cpp

(1)编写Makefile

HelloWorld : HelloWorld.cpp
  g++ HelloWorld.cpp -o HelloWorld
clean :
  rm HelloWorld

(2)编译

make

(3)清空

make clean

1.2 构成

Makefile主要由多条规则构成,每条规则由三部分构成:目标(target)、依赖(prerequiries)和命令(command)。

1.3 格式

按如下格式编写Makefile

目标(target): 依赖(prerequiries)...
  命令(command)

(1)目标(target)通常是要产生的文件的名称,目标的例子是可执行文件或OBJ文件。目标也可是一个执行的动作名称,诸如‘clean’(仅仅表达动作的目标称为假想目标)。
(2)依赖是用来输入从而产生目标的文件,一个目标经常有几个依赖。
(3)命令是Make执行的动作,一个规则可以含有几个命令,每个命令占一行。
注意:
每个命令行前面必须是一个Tab字符,即命令行第一个字符是Tab。这是不小心容易出错的地方

1.4 说明:

(1)默认情况下,make最先执行第一条。
(2)使用make 目标名的方式,执行指定的规则。

2、Makefile多文件编译

2.1 示例

(1)string.h

#ifndef _STRING_H_
#define _STRING_H_
#include <iostream>
using namespace std;
#include <string.h>
class String{
public:
    String(const char* cstr = NULL);
    String(const String& str);
    String& operator=(const String& str);
    ~String();
    char* c_str() const {
        return m_data;
    }
private:
    char* m_data;
};
ostream& operator<<(ostream& os, const String& str);
#endif // _STRING_H_

(2)string.cpp

#include "String.h"
String::String(const char* cstr /*= NULL*/) {
    if (cstr) {
        m_data = new char[strlen(cstr) + 1];
        strcpy(m_data, cstr);
    }
    else {
        m_data = new char[1];
        *m_data = '\0';
    }
}
String::String(const String& str) {
    m_data = new char[strlen(str.m_data) + 1];
    strcpy(m_data, str.m_data);
}
String& String::operator=(const String& str) {
    //检测是否自我赋值
    if (this == &str)
        return *this;
    delete [] m_data;
    m_data = new char[strlen(str.m_data) + 1];
    strcpy(m_data, str.m_data);
    return *this;
}
String::~String() {
    delete[] m_data;
}
ostream& operator<<(ostream& os, const String& str) {
    os << str.c_str();
    return os;
}

(3)StringTest.cpp

#include "String.h"
int main() {
    String s1;
    String s2("hello");
    String s3(s1);  //拷贝构造函数
    cout << s3 << endl;
    s3 = s2;    //拷贝赋值函数
    cout << s3 << endl;
    return 0;
}

(4)makefile

StringTest:StringTest.o String.o
  g++ -o StringTest StringTest.o String.o
StringTest.o:StringTest.cpp String.h
  g++ -c StringTest.cpp
String.o:String.cpp String.h
  g++ -c String.cpp
clean :
  rm StringTest StringTest.o String.o

2.2 说明

make执行规则之前,检查依赖是否存在或者是否最新的。如果不是则执行依赖对应的规则,创建或者更新依赖。

3、使用变量简化makefile

**优点:**每次增加新的文件,需要在nakefile的很多地方增加依赖,容易导致遗漏。可以使用变量可以简化,避免这种出错的可能。
变量定义:变量=字符串
变量使用:$(变量名)

3.1 示例

(1)makefile

OBJS = StringTest.o String.o

StringTest:$(OBJS)
  g++ -o StringTest $(OBJS)
StringTest.o:StringTest.cpp String.h
  g++ -c StringTest.cpp
String.o:String.cpp String.h
  g++ -c String.cpp
clean :
  rm StringTest $(OBJS)

在makefile文件中使用名为objects, OBJECTS, objs, OBJS, obj, 或 OBJ的变量代表所有OBJ文件已是约定成俗。

3.2 说明

变量是定义一个字符串,在多处替代该字符串使用。

4、命令自动推导

编译.o文件这类非常普遍并且常用,规则也比较简单

文件名.o:文件名.cpp 头文件
  g++ -c 文件名.cpp

make提供一种简化写法,可以自动推导出该规则

文件名.o:头文件

这种简化规则称为隐含规则,非简化规则成为具体规则。

4.1 示例

(1)makefile

OBJS = StringTest.o String.o

StringTest:$(OBJS)
  g++ -o StringTest $(OBJS)
StringTest.o:String.h
String.o:String.h

clean :
  rm StringTest $(OBJS)

(2)小知识

通常,规则按照目标进行分组。规则也可以按照依赖分组。例如,例子中String.o和StringTest.o都依赖String.h。那么,可以这两个合并到一个规则中。

OBJS = StringTest.o String.o

StringTest:$(OBJS)
 g++ -o StringTest $(OBJS)
StringTest.o String.o:String.h

clean :
 rm StringTest $(OBJS)

按照依赖分组规则可以减少规则数量,规则按照目标分组更符合我们日常思维习惯。

5、假想动作

表达动作的目标称为假想目标。通常规则会生成或者更新与目标的同名文件,但是假想目标不生成文件,只是作为几个命令组成特殊规则的名称。例如例子中的clean,只是执行清理动作。**如果,makefile同级目录存在与假象目标同名的文件(例如:clean),那么会导致命令不会被执行。**所以需要把目标显示声明为假想目标。

5.1 示例

(1)makefile

OBJS = StringTest.o String.o

.PHONY: all clean

all:StringTest

StringTest:$(OBJS)
  g++ -o StringTest $(OBJS)
StringTest.o:String.h
String.o:String.h

clean :
  rm StringTest $(OBJS)

(2)常用假想目标

6、通配符与变量

编译.o文件可以写成更通用的方式,使用我们之前已经定义好的变量$(OBJS),自动推导出需要生成的规则。

6.1 makefile

OBJS = StringTest.o String.o

.PHONY: all clean

all:StringTest

StringTest:$(OBJS)
  g++ -o StringTest $^
$(OBJS):%.o:%.cpp
  $(CXX) -c $(CXXFLAGS) $< -o $@

.PHONY: clean

clean :
  rm StringTest $(OBJS)

6.2 说明

(1)通配符

通配符主要用于匹配文件名,makefile中使用%作为通配符。从匹配目标格式的目标名中依据通配符抽取部分字符串,再按照抽取字符串分配到每一个依赖格式中产生依赖名。例如,使用%.o:%.cpp。

(2)自动变量

自动变量是在规则每次执行时都基于目标和依赖产生新值的变量。下面是常用的自动变量。
在这里插入图片描述
在这里插入图片描述

(3)预定义变量

预定义变量是makefile已经定义好的变量,用户可以在makefile文件中改变变量的值。
程序名变量
在这里插入图片描述
程序运行参数的变量
在这里插入图片描述

7、其他

注释#
换行
回显命令@echo

8、总结

在这里插入图片描述

9、常见makefile的用法汇总

#规则:(命令构成的一组操作)
#默认只执行第一条规则 
#若想执行其他规则,make+命令名字
#目标:依赖
#	命令
#
#g++ overload.o Test.o

#只执行第一条规则,然后先找依赖在下面存在不存在,如果存在,先执行依赖,再执行第一条,依赖是串联规则执行的条件
#表示前面的TARGET,$^表示前面的OBJECTS,$<表示第一个依赖。

#前面的总的定义
TARGET=Test  #生成的文件名
OBJECTS = overload.o\
		  Test.o#变量做替换的
#CXX=clang  CXX可以改成其他的编程方式,例如gcc等等
CXXFLAGS=-g #可以变成调试版本的
.PHONY: clean
#虚假目标,用来无视其他干扰,只执行makefile里面的命令
#第一条规则,也是总的规则
all:$(TARGET)
$(TARGET):$(OBJECTS)
	@echo 生成可执行文件$(TARGET)
	$(CXX) $(CXXFLAGS) $^ -o $@

# overload.o:overload.cpp
# 	$(CXX) $(CXXFLAGS) -c $<

# Test.o:Test.cpp
#  	$(CXX) $(CXXFLAGS) -c $<

#%表示通配符
#通配符推导
$(OBJECTS):%.o:%.cpp
	$(CXX) $(CXXFLAGS) -c $<

clean:
	@echo 清空文件
	$(RM) Test *.o
举报

相关推荐

0 条评论