Makefile入门

作者: jdzhangxin | 来源:发表于2018-03-30 15:27 被阅读85次

    0. 作用

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

    1. Makefile基本语法与执行

    示例

    编译一个单文件HelloWorld.cpp

    • 编写Makefile
    HelloWorld : HelloWorld.cpp
      g++ HelloWorld.cpp -o HelloWorld
    clean :
      rm HelloWorld
    
    • 编译
    make
    
    • 清空
    make clean
    

    构成

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

    格式

    按如下格式编写Makefile

    目标(target): 依赖(prerequiries)...
      命令(command)
    
    • 目标(target)通常是要产生的文件的名称,目标的例子是可执行文件或OBJ文件。目标也可是一个执行的动作名称,诸如‘clean’(仅仅表达动作的目标称为假想目标)。
    • 依赖是用来输入从而产生目标的文件,一个目标经常有几个依赖。
    • 命令是Make执行的动作,一个规则可以含有几个命令,每个命令占一行。

    注意:每个命令行前面必须是一个Tab字符,即命令行第一个字符是Tab。这是不小心容易出错的地方。

    说明

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

    2. Makefile多文件编译

    示例

    • 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_
    
    • 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;
    }
    
    • 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;
    }
    
    • 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
    

    说明

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

    3. 使用变量简化makefile

    每次增加新的文件,需要在makefile的很多地方增加依赖,容易导致遗漏。可以使用变量可以简化,避免这种出错的可能。

    • 变量定义:变量 = 字符串
    • 变量使用:$(变量名)

    示例

    • 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文件已是约定成俗。

    说明

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

    4. 命令自动推导

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

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

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

    文件名.o:头文件
    

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

    示例

    • makefile
    OBJS = StringTest.o String.o
    
    StringTest:$(OBJS)
      g++ -o StringTest $(OBJS)
    StringTest.o:String.h
    String.o:String.h
    
    clean :
      rm StringTest $(OBJS)
    
    • 小知识
      通常,规则按照目标进行分组。规则也可以按照依赖分组。例如,例子中String.oStringTest.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),那么会导致命令不会被执行。所以需要把目标显示声明为假想目标。

    .PHONY 目标
    

    示例

    • 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)
    

    • 常用假想目标
    No. 假想目标 功能
    1 all 这所有目标的目标,一般是编译所有的目标。
    2 clean 删除所有被make创建的文件。
    3 install 安装已编译好的程序,就是把目标执行文件拷贝到指定的目标中去。
    4 print 列出改变过的源文件。
    5 tar 源程序打tar包备份
    6 dist 创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。

    6. 通配符与变量

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

    • 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)
    

    说明

    1. 通配符

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

    2. 自动变量

    自动变量是在规则每次执行时都基于目标和依赖产生新值的变量。下面是常用的自动变量。

    No. 自动变量 含义
    1 $< 表示第一个匹配的依赖
    2 $@ 表示目标
    3 $^ 所有依赖
    3. 预定义变量

    预定义变量是makefile已经定义好的变量,用户可以在makefile文件中改变变量的值。

    • 程序名变量
    变量 程序 默认值
    CC C语言编译程序 cc
    CXX C++编译程序 g++
    CPP 带有标准输出的C语言预处理程序 $(CC) -E
    • 程序运行参数的变量
    变量 程序参数
    CFLAGS 用于C编译器的额外标志
    CXXFLAGS 用于C++编译器的额外标志

    9. 其他

    • 注释#
    • 换行\
    • 回显命令@echo

    10. 总结

    参考

    相关文章

      网友评论

        本文标题:Makefile入门

        本文链接:https://www.haomeiwen.com/subject/bkdrcftx.html