zoukankan      html  css  js  c++  java
  • (5)Makefile详解

         Makefile是一个自动化的编译工具,关系到整个工程的编译规则,极大的提高了软件开发的效率。

        (1)Makefile的编译规则

    //Makefile 也可以写作 makefile
    1
    )如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。 2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。 3)如果这个工程的头文件被改变了,那么我们需要编译引用了这个头文件的所有C文件,并链接目标程序。

        (2)Makefile的书写规则

        规则的三个要素:目标、依赖、命令

     //Makefile格式 
    target ... : prerequisites ... command

    //解释
    target:目标文件,可以是object file,也可以是可执行文件,还可以是一个标签;
    prerequisites:生成target所需要的目标或者文件;
    command:任意shell命令

        (3)Makefile的工作原理

    //当执行make命令生成目标文件时
    1.make命令使用时会在当前目录下寻找名为Makefile或者makefile的文件;
    2.如果找到,它将会把文件中第一个target当作最终的目标文件;
    3.如果target已然存在,并且它所依赖的所有文件的修改时间都没更改,则返回;否则执行后面的内容;
    4.一层一层往前推,类似一个堆栈,直到到达所依赖的文件是.h和.c或者.cpp文件为止,此时便可以链式推导生成最终的目标文件了;

        (4)gcc和g++命令概述

        1)gcc和g++简介

        GCC(GNU Compiler Collection)    GNU编译器套件

        gcc和g++均是GCC的一部分,gcc是GNU的c编译器,g++是GNU的c++编译器;

        gcc将后缀名为.c的文件当作c程序,将后缀名为.cpp的文件当作c++程序;

        g++将后缀名为.c和cpp的均当作c++程序;

        2)区别

        g++和gcc均可以编译链接c或者c++程序,使用方法有稍微的区别,g++编译链接c程序时可能调用gcc;gcc编译链接c++时,由于c++是c的超集,需要指定使用c++的动态库libstdc++.so;

        范例如下,是等价的

    // 存在一个main.cpp
    g++ main.cpp -o test
    // 链库的-l参数必须放置在源码之后 gcc main.cpp
    -lstdc++ -o test
    gcc main.cpp -o test -lstdc+

        3) GNU的编译步骤

    1.预处理(Preprocessing)
    //由预处理器cpp完成,将.cpp源文件预处理为.i文件。 g
    ++ -E test.cpp -o test.i 2.编译(Compilation)。由编译器cc1plus完成,将.i文件编译为.s的汇编文件。使用-S选项,只进行编译而不进行汇编,生成汇编代码。 g++ -S test.i -o test.s 3.汇编(Assembly)。由汇编器as完成,将.s文件汇编成.o的二进制目标文件。 g++ -c test.s -o test.o 4.链接(Linking)。由链接器ld,将.o文件连接生成可执行程序。 g++ test.o -o test.out
    g++ test.o -o test

        (5)Makefile使用简介

        1)变量的定义和使用

    #定义编译器
    cc = gcc 
    或者
    cc = g++
    #定义编译参数  -w 不显示任何警告信息  -W 只显示错误警告信息  -Wall 显示所有警告信息
    CFLAG = -g  -Wall -W
    #定义待链接的库 -L后指明待链接的库所在的路径 -l参数待链接的库名
    LIBRARY + = -L /usr/lib/ -lstdc++
    #添加宏定义
    DEBUG = MYDEF
    DEF = -D$(DEBUG) #使用 -o参数建议放在编译命令最后,否则可能会将源文件删除 -g参数必须放置在-o参数之前 $(cc) $(CFLAG) $(DEF) $(源文件) -o $(目标文件) $(LIBRARY)

        在变量的使用过程中,分为两种,递归展开式变量和直接展开式变量

    //递归展开式变量
    A = $(B)
    B = $(C)
    C = ME
    则 A = ME,其优点是前面使用的变量可以使用后续定义的变量的值,缺点是有可能陷入无限循环
    //直接展开式变量
    A = aaa
    B := $(A)bbb
    A = ccc
    则 B的值为 aaabbb,后续定义的A的值与其无关
    如果写法为
    A = aaa
    B = $(A)bbb
    A = ccc
    则最终 B的值为 cccbbb

        2)$相关变量

    $^    所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
    $@    表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合
    $?    所有比目标新的依赖目标的集合。以空格分隔。
    $<    依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
    $(@D) 表示"$@"的目录部分(不以斜杠作为结尾),如果"$@"值是"dir/foo.o",那么"$(@D)"就是"dir",而如果"$@"中没有包含斜杠的话,其值就是"."(当前目录) 。 
    $(@F) 表示"$@"的文件部分,如果"$@"值是"dir/foo.o",那么"$(@F)"就是"foo.o""$(@F)"相当于函数"$(notdir $@)"
    // 例1
    %.o : %.c
    gcc  -c  $<  -o  $@
    把所以的c文件编译生成对应的o文件,$<代表每次取的c文件,$@代表每次c文件对应的目标文件
    // 例2
    main : main.o  test.o  test1.o  test2.o
    gcc  -o  $@  $^
    把所有的o文件编译生成可执行的main文件,$^代表所以的依赖文件集合(main.o  test.o  test1.o  test2.o),@代表目标文件(main)
    // 例3
    lib : test.o  test1.o  test2.o
    ar r lib $?
    把有更新的依赖文件重新打包到库lib中, 如果只有test1.o更新,则$?代表test1.o, 如果test.o  test1.o都有更新,则$?代表test.o  test1.o的集合。

        3)伪目标 clean build rebuild all

    CC := gcc
    Target := helloworld.out
    
    $(Target) : func.o main.o
        $(CC) -o $(Target) main.o func.o
    
    main.o : main.c
        $(CC) -c main.c -o main.o
    func.o : func.c
        $(CC) -c func.c -o func.o
    
    .PHONY : rebuild clean build
    
    rebuild : clean build
    
    build : $(Target)
    #    @echo "build"    
    clean :
    #    @echo "clean"
        rm *.o $(Target)

        在上述Makefile中,clean伪目标作用是将编译链接过程中所有生成文件全部删除,回到make执行的初始状态;build的作用是生成target,与make的作用相同;rebuild伪目标依赖于clean 和 build,当用户输入make rebuild时,实际上等效于make clean ,make build依此执行,build依赖于target,此时会将target重新生成,整个工程因此重新编译链接并生成。all伪目标适用于在同一个makefile中生成多个目标库文件时使用。

        4)函数

        GNU make提供了很多的函数,可以在Makefile文件中调用这些函数来进行文件名、变量以及命令等的处理。

    1) patsubst  主要对字符串进行运算和分析
    用法:$(patsubst pattern,replacement,text)
    功能:将text文本中出现的所有pattern替换为replacement
    例子:$(patsubst %.c,%.o,a.c,b.o,c.c)
    输出:a.o,b.o,c.o
    (2) dir 主要用于获取文件的路径
    用法:  $(dir text)
    功能: 将text中所有文件的对应目录输出
    例子: $(dir main.cpp,libstdc++d)
    输出   ./ , /usr/lib/3)notdir 抽取除去路径意外的其它信息
    用法: $(notdir text)
    功能: 去除text中所有包含的路径,只留下文件信息
    例子: $(notdir /home/perfect/Mywork/C/main.c ./Makefile)
    输出: main.c Makefile
    (4) suffix 获得后缀名
    用法:$(suffix text)
    功能:将text中所有文件只留下后缀名
    例子:$(suffix a.c,b.c)
    输出 .c,.c
    (5) addsuffix 给源目标文件添加前缀
    用法:$(addprefix param,text)
    功能:将text中每个源文件添加上合适的前缀后缀后输出
    例子:$(addprefix -l,$(LIBS))
    输出:完成的库名
    (6) wildcard 扩展通配符
    用法:$(wildcard PATTERN...)
    功能:获取所有复合PATTERN格式的文件
    例子:$(wildcard *.c)
    输出:当前目录下所有的.c文件
    复杂例子:$(patsubst %.c,%.o,$(wildcard *.c))
    首先获取当前路径下所有的.c文件,然后将.c后缀名更改为.o后缀名并返回;

        5)include关键字

        include命令用于将最新的子Makefile包含进当前Makefile文件,再根据当前Makefile对文件进行编译链接;适用于当系统过大时,Makefile复杂时进行拆分。

        (6)Makefile简单范例

    //clac_test.h
    #ifndef _CALC_TEST_H_
    #define _CALC_TEST_H_ namespace test { int add(int a,int b); }
    //calc_test.cpp
    #include "calc_test.h" namespace test { int add(int a,int b) { return a + b ; } }
    //make_test.h
    #ifndef _MAKE_TEST_
    #define _MAKE_TEST_ #include <iostream> namespace test { class MakeTest { public: void run(); }; } #endif
    //make_test.cpp
    #include "make_test.h" #include "calc_test.h" namespace test { void MakeTest::run() { int a = 10; int b = 10; std::cout<<test::add(a,b)<<std::endl; } }
    //main.cpp
    #include "string.h"
    #include "make_test.h"
    
    using namespace std;
    
    int main()
    {
      test::MakeTest makeTest;// = new MakeTest();
      makeTest.run();
      return 0;
    }

        方法1:直接使用g++命令

    g++ calc_test.cpp  make_test.cpp main.cpp -o test

        方法2:直接使用gcc命令

    gcc calc_test.cpp make_test.cpp main.cpp -lstdc++ -o test

        方法3:全量的Makefile

    test : main.o calc_test.o make_test.o
            g++ main.o calc_test.o make_test.o -o test
    main.o : main.cpp
            g++ -c main.cpp -o main.o
    calc_test.o : calc_test.cpp
            g++ -c calc_test.cpp -o calc_test.o
    make_test.o : make_test.cpp
            g++ -c make_test.cpp -o make_test.o
    main.cpp : make_test.h
    make_test.cpp : make_test.h calc_test.h
    calc_test.cpp : calc_test.h
    .PHONY : clean
    clean :
            rm test *.o

        方法4:Makefile(隐晦推导)

    test : main.o calc_test.o make_test.o
            g++ main.o calc_test.o make_test.o -o test
    main.o : make_test.h main.cpp
            g++ -c main.cpp make_test.cpp
    make_test.o : make_test.h calc_test.h
            g++ -c make_test.cpp calc_test.cpp
    calc_test.o : calc_test.h
            g++ -c calc_test.cpp
    .PHONY: clean
    clean:
            rm test *.o

        方法5:Makefile(变量定义)

    objects = calc_test.o make_test.o main.o
    
    test:$(objects)
            gcc -o test $(objects) -lstdc++
    $(objects):calc_test.h make_test.h
    .PHONY:clean
    clean:
            rm test *.o

        (7)Makefile处理多目标文件

        1)单一Makefile利用伪目标 all

         新建文件test.cpp 编译为 temp库

    //test.cpp
    #include <iostream> int main() { std::cout<<10<<std::endl; return 0; }
    //Makefile
    all : test temp
    temp : test.cpp
            gcc test.cpp -o temp -lstdc++
    test : calc_test.cpp make_test.cpp main.cpp
            g++ calc_test.cpp make_test.cpp main.cpp -o test
    .PHONY : clean
    clean :
            rm test temp

        2)当有多个Makefile时,每个Makefile一个目标文件

        新建rapidmain.cpp(当前路径下有rapidxml的所有头文件)

    //rapidmain.cpp
    #include <iostream> #include "rapidxml_print.hpp" #include "rapidxml_utils.hpp" #include "rapidxml.hpp" using namespace std; int main() { rapidxml::xml_document<> doc; rapidxml::xml_node<> *declaration = doc.allocate_node(rapidxml::node_declaration); declaration->append_attribute(doc.allocate_attribute("version","1.0")); declaration->append_attribute(doc.allocate_attribute("encoding","utf-8")); doc.append_node(declaration); std::cout<<doc<<endl; return 0; }

        新建Makefile.rapidxml文件

    rapid : rapidmain.cpp
            g++ -I ./rapidxml/ rapidmain.cpp -o rapid
    .PHONY : clean
            rm rapid

         新建Makefile.maketest文件

    objects = calc_test.o make_test.o main.o
    
    test:$(objects)
            gcc -o test $(objects) -lstdc++
    $(objects):calc_test.h make_test.h
    .PHONY :clean
    clean:
            rm test *.o

        方法1.新建总Makefile(采用伪目标和make命令)

    all :
            make -f Makefile.rapidxml
            make -f Makefile.maketest
    // make -C [路径名] 进入指定路径执行make命令

       方法2.新建总 Makefile(多个目标时,使用include包含子Makefile)

    all : test rapid
    include Makefile.maketest
    include Makefile.rapidxml

     

  • 相关阅读:
    Eclipse中配置约束(DTD,XSD)
    Eclipse集成tomcat
    java使用dom4j对XML进行CURD操作
    SQL数据库操作(CURD)
    Java-IO流总结
    Java-集合框架总结
    AES apache commons-crypto 对称加密
    Redis
    Axis2 客户端调用 设置超时时间
    Sybase 修改数据库默认排序
  • 原文地址:https://www.cnblogs.com/MenAngel/p/11574787.html
Copyright © 2011-2022 走看看