【Linux开发—Makefile工具】

一,背景

  • 一个项目通常有多个源文件,如果只修改其中一个,就对所有源文件重新执行编译、链接步骤,就太浪费时间了。因此十分有必要引入 Makefile 工具:Makefile 工具可以根据文件依赖,自动找出那些需要重新编译和链接的源文件,并对它们执行相应的动作。

二,认识

1,make与makefile

  • Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
  • Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作
  • 而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。
  • makefile 文件是许多编译器–包括 Windows下的编译器–维护编译信息的常用方法,只是在集成开发环境中,用户通过友好的界面修改 makefile 文件而已。
  • 在 UNIX 系统中,习惯使用 Makefile 作为 makefile 文件。如果要使用其他文件作为 makefile,则可利用类似的 make 命令选项指定 makefile 文件。
  • 一个文件,指示程序如何编译和链接程序。makefile文件的默认名称是名副其实的Makefile,但可以指定一个命令行选项的名称。
  • make程序有助于您在开发大型程序跟踪整个程序,其中部分已经改变,只有那些编译自上次编译的程序,它已经改变了部分。

2,关于编译

编译一个小的C程序至少需要一个单一的文件.h文件(如适用)。虽然命令执行此任务只需CC file.c中,有3个步骤,以取得最终的可执行程序,如下所示:

  • 编译阶段:所有的C语言代码.c文件中被转换成一个低级语言汇编语言;决策.s文件。

  • 汇编阶段:前阶段所作的汇编语言代码,然后转换成目标代码的代码片段,该计算机直接理解。目标代码文件.o 结束。

  • 链接阶段:编译程序涉及到链接的对象代码的代码库,其中包含一定的“内置”的功能,如printf的最后阶段。这个阶段产生一个可执行程序,默认情况下,这是名为a.out。

3,编译案例:

假定有以下的源文件。

main.cpp
hello.cpp
factorial.cpp
functions.h
  • main.cpp 文件的内容
#include <iostream.h>

#include "functions.h"

int main(){
    
    
    print_hello();
    cout << endl;
    cout << "The factorial of 5 is " << factorial(5) << endl;
    return 0;
}
  • hello.cpp 文件的内容
#include <iostream.h>

#include "functions.h"

void print_hello(){
    
    
   cout << "Hello World!";
}
  • factorial.cpp 文件的内容
#include "functions.h"

int factorial(int n){
    
    
    if(n!=1){
    
    
	return(n * factorial(n-1));
    }
    else return 1;
}
  • functions.h 内容
void print_hello();
int factorial(int n);

琐碎的方法来编译的文件,并获得一个可执行文件,通过运行以下命令:

CC  main.cpp hello.cpp factorial.cpp -o hello
  • CC:代表编译器(gcc),编译C++文件,
  • 这上面的命令将生成二进制的Hello。在我们的例子中,我们只有四个文件,我们知道的函数调用序列,因此它可能是可行的,上面写的命令的手,准备最后的二进制。但对于大的项目,我们将有源代码文件成千上万的文件,就很难保持二进制版本。
  • make命令允许您管理大型程序或程序组。当开始编写较大的程序,你会发现,重新编译较大的程序,需要更长的时间比重新编译的短节目。此外会发现通常只能在一小部分的程序(如单一功能正在调试),其余的程序不变。

三,Makefile 宏

make程序允许您使用宏,这是类似的变量。 = 一对一个Makefile中定义的宏。例如:

MACROS=  -me
PSROFF=  groff -Tps
DITROFF= groff -Tdvi
CFLAGS= -O -systype bsd43
LIBS := "-lncurses -lm -lsdl"
LIBS += -lstdc++
MYFACE = ":*)"
PROJ_HOME = /home/moon/projects
$(MACROS)
$(MYFACE) :*)

1,特殊的宏($@$?, $<, $*,$^

1,$@$?

目标规则集发出任何命令之前,有一些特殊的预定义宏。
- $@ 表示目标文件。
- $? 表示比目标还要新的依赖文件列表

因此,举例来说,我们可以使用一个规则:

hello: main.cpp hello.cpp factorial.cpp
	$(CC) $(CFLAGS) $? $(LDFLAGS) -o $@

alternatively:

hello: main.cpp hello.cpp factorial.cpp
        $(CC) $(CFLAGS) $@.cpp $(LDFLAGS) -o $@
  • 在这个例子中$@代表 hello, ? 或 ? 或 ?@.cpp将拾取所有更改的源文件。

2,$<, $*

有两个比较特殊的隐含规则中使用的宏。它们是:

  • $< 表示第一个依赖文件
  • $* 这个变量表示目标模式中“%”及其之前的部分.o文件 等效于%.o文件
    • 对于$*如果目标是“dir/a.foo.b”,并且目标的模式是“a.%.b”,那么,“$*”的值就是“dir/a.foo”。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么“$*”也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么“$*”就是除了后缀的那一部分。例如:如果目标是“foo.c”,因为“.c”是make所能识别的后缀名,所以,“$*”的值就是“foo”。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用“$*”,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么“$*”就是空值。。
      常见的隐含规则的构造 .o(对象)文件,.cpp(源文件)。
.o:.cpp
        $(CC) $(CFLAGS) -c $<

alternatively

.o:.cpp
        $(CC) $(CFLAGS) -c $*.cpp

3,$^

$^ 表明所有的依赖项:

LDFLAGS = -lstdc++
hello:main.o factorial.o hello.o
	$(CC) $^ $(LDFLAGS ) -o $@

2,传统宏

有很多默认的宏(输入“make -p”打印出来的默认值)。大多数使用它们的规则是很明显的:
这些预定义变量,即。在隐含规则中使用的宏分为两大类:那些程序名(例如CC)和那些含有参数的程序(如CFLAGS)。

下面是一些比较常见的变量用作内置规则:makefile文件的程序名称的表。

  • AR Archive-maintaining program; default `ar’.
  • AS Program for compiling assembly files; default `as’.
  • CC Program for compiling C programs; default `cc’.
  • CO Program for checking out files from RCS; default `co’.
  • CXX Program for compiling C++ programs; default `g++'.
  • CPP Program for running the C preprocessor, with results to standard output; default `$(CC) -E’.
  • FC Program for compiling or preprocessing Fortran and Ratfor programs; default `f77’.
  • GET Program for extracting a file from SCCS; default `get’.
  • LEX Program to use to turn Lex grammars into source code; default `lex’.
  • YACC Program to use to turn Yacc grammars into source code; default `yacc’.
  • LINT Program to use to run lint on source code; default `lint’.
  • M2C Program to use to compile Modula-2 source code; default `m2c’.
  • PC Program for compiling Pascal programs; default `pc’.
  • MAKEINFO Program to convert a Texinfo source file into an Info file; default `makeinfo’.
  • TEX Program to make TeX dvi files from TeX source; default `tex’.
  • TEXI2DVI Program to make TeX dvi files from Texinfo source; default `texi2dvi’.
  • WEAVE Program to translate Web into TeX; default `weave’.
  • CWEAVE Program to translate C Web into TeX; default `cweave’.
  • TANGLE Program to translate Web into Pascal; default `tangle’.
  • CTANGLE Program to translate C Web into C; default `ctangle’.
  • RM Command to remove a file; default `rm -f’.

这里是一个变量,其值是上述程序的额外的参数表。所有这些的默认值是空字符串,除非另有说明。

  • ARFLAGS Flags to give the archive-maintaining program; default `rv’.
  • ASFLAGS Extra flags to give to the assembler (when explicitly invoked on a .s' or .S’ file).
  • CFLAGS Extra flags to give to the C compiler.
  • CXXFLAGS Extra flags to give to the C compiler.
  • COFLAGS Extra flags to give to the RCS co program.
  • CPPFLAGS Extra flags to give to the C preprocessor and programs that use it (the C and Fortran compilers).
  • FFLAGS Extra flags to give to the Fortran compiler.
  • GFLAGS Extra flags to give to the SCCS get program.
  • LDFLAGS Extra flags to give to compilers when they are supposed to invoke the linker, `ld’.
  • LFLAGS Extra flags to give to Lex.
  • YFLAGS Extra flags to give to Yacc.
  • PFLAGS Extra flags to give to the Pascal compiler.
  • RFLAGS Extra flags to give to the Fortran compiler for Ratfor programs.
  • LINTFLAGS Extra flags to give to lint.

注:您可以取消-R--no-builtin-variables选项隐含规则使用的所有变量。
如,也可以在命令行中定义的宏

          make CPP=/home/moon/projects

四,Makefile定义依赖性

新版的gcc编译器引入头文件不再需要后缀名,eg:# include<iostream>,不再需要.h后缀。

这是很常见的,最终的二进制文件将依赖于各种源代码和源代码的头文件。依存关系是重要的,因为他们告诉对任何目标的源。请看下面的例子

hello: main.o factorial.o hello.o
	$(CC) main.o factorial.o hello.o -o hello
  • 在这里,我们告诉目标文件hello 它依赖main.o,factorial.o和hello.o,所以每当有任何变化,这些目标文件将采取行动,也可使用编译规则规则:$(CC) $? -o $@来替代$(CC) main.o factorial.o hello.o -o hello

同时我们会告诉如何准备 .o文件,所以我们必须定义这些依赖也如下:

main.o: main.cpp functions.h
	$(CC) -c main.cpp

factorial.o: factorial.cpp functions.h
	$(CC) -c factorial.cpp

hello.o: hello.cpp functions.h
	$(CC) -c hello.cpp

如果依赖项多时,就在LDFLAGS里面 += 来连接
在这里插入图片描述

五,Makefile定义规则

一个Makefile目标规则的一般语法

    target [target...] : [dependent ....]
    [ command ...]
  • 方括号中的项是可选的,省略号是指一个或多个。注意标签,每个命令前需要。

下面给出一个简单的例子,定义了一个规则使您的目标从 hello 其他三个文件。

hello: main.o factorial.o hello.o
	$(CC) $? -o $@
  • 注:在这个例子中,你必须放弃规则,使所有对象从源文件读文件

语义是相当简单的。当"make target"发现目标规则适用,如有眷属的新目标,使执行的命令一次一个(后宏替换)。如果有任何依赖进行,即先发生(让您拥有一个递归)。
如果有任何命令返回一个失败状态,MAKE将终止。这就是为什么看到规则,如:

clean:hello
        -rm *.o *~ core paper
  • Make忽略一个破折号开头的命令行返回的状态。例如。如果没有核心文件,谁在乎呢?,定义完clean后,执行:make clean,清理所有文件

Make 会 echo 宏字符串替换的命令后,告诉发生了什么事,因为它发生。有时可能想要把它们关掉。例如:

install:hello
        @echo You must be root to install
  • @加在echo命令前,表示静默输出,不会打印出跟信息无关的东西,比如不会打印出echo,只会打印出You must be root to install。
  • 大家所期望的Makefile的 某些目标。应该总是先浏览,但它的合理预期的目标(或只是做),安装,清理。
    • make all - 编译一切,让你可以在本地测试,之前安装的东西,all可以把规则一起使用,eg:all:hello install
    • make install - 应安装在正确的地方的东西。但看出来的东西都安装在正确的地方为系统。
    • make clean - 应该清理的东西。摆脱的可执行文件,任何临时文件,目标文件等。

六,Makefile的隐含规则

1,该命令应该在所有情况下,我们建立一个可执行x的的源代码x.cpp的作为一个隐含的规则,这可以说:

.cpp:
        $(CC) $(CFLAGS) $@.cpp $(LDFLAGS) -o $@
  • 这种隐含的规则说,如果make c, x.c 运行x.c 调用输出x。规则是隐式的,因为没有特定的目标提到。它可用于在所有的情况下。

2,另一种常见的隐含规则的构造 .o(对象)文件和 .cpp (源文件)。

.o:.cpp
        $(CC) $(CFLAGS) -c $<

alternatively

.o:.cpp
        $(CC) $(CFLAGS) -c $*.cpp

3,$^ 表明所有的依赖项

#LDFLAGS 表示依赖库
LDFLAGS = -lstdc++ 
hello:main.o factorial.o hello.o
        $(CC) $^ $(LDFLAGS) -o $@
.o:.cpp functions.h
        $(CC) -c $<
clean:
        -rm *.o hello
install:hello
        @echo "You need build hello!"
        echo "no @ used"
all:hello install

七,Makefile 自定义后缀规则

就其本身而言,make已经知道,为了创建一个 .o文件,就必须使用 cc-c 相应的c文件。 建成MAKE这些规则,可以利用这一点来缩短Makefile。如果仅仅只是表示 .h 文件的 Makefile依赖线,依赖于目前的目标是,MAKE会知道,相应的文件已规定。你甚至不需要编译器包括命令。
这减少了我们的Makefile更多,如下所示:

OBJECTS = main.o hello.o factorial.o
hello: $(OBJECTS)
        cc $(OBJECTS) -o hello
hello.o: functions.h
main.o: functions.h 
factorial.o: functions.h 

1, .SUFFIXES 允许自定义后缀

Make 使用一个特殊的目标,故名 .SUFFIXES允许你定义自己的后缀。例如,依赖项:

.SUFFIXES: .foo .bar
  • 告诉make ,将使用这些特殊的后缀,以使自己的规则。

如何让 make 已经知道如何从 .c 文件生成 .o文件。类似的可以定义规则以下列方式:

.foo:.bar
        tr '[A-Z][a-z]' '[N-Z][A-M][n-z][a-m]' < $< > $@
.c:.o
        $(CC) $(CFLAGS) -c $<
  • 第一条规则允许你创建一个 .bar 文件从 .foo文件。 (不要担心它做什么,它基本上打乱文件)
  • 第二条规则 .c文件创建一个 .o 文件中使用的默认规则。

2,* 是一个通配符

*用来表示任意的词,可以极大的简化makefile:

LDFLAGS = -lstdc++

all:hello install

hello:*.cpp *.h
        $(CC) $^ $(LDFLAGS) -o $@

clean:
        -rm *.o hello

install:hello
        @echo "You need build hello!"
        echo "no @ used"

八,Makefile指令

有好几种指令以不同的形式。让程序可能不支持所有指令。因此,请检查make是否支持指令,这里我们探索GNU make支持的一些指令

1,条件的指令

条件指令:

  • ifeq 指令开始的条件,指定的条件。它包含两个参数,用逗号分隔,并用括号括起。两个参数进行变量替换,然后对它们进行比较。该行的makefile继IFEQ的服从如果两个参数的匹配,否则会被忽略。是否相等
  • ifneq 指令开始的条件,指定的条件。它包含两个参数,用逗号分隔,并用括号括起。两个参数进行变量替换,然后对它们进行比较。makefile ifneq 遵守如果两个参数不匹配,否则会被忽略。是否不等
  • ifdef 指令开始的条件,指定的条件。它包含单参数。如果给定的参数为真,则条件为真。是否定义
  • ifndef 指令开始的条件,指定的条件。它包含单参数。如果给定的是假的,那么条件为真。是否没定义
  • else 指令会导致以下行如果前面的条件未能被遵守。在上面的例子中,这意味着第二个选择连接命令时使用的第一种选择是不使用。它是可选的,在有条件有一个else。
  • endif 指令结束条件。每一个条件必须与endif结束。

1,条件式指令的语法

一个简单的条件
没有其他的语法如下:

conditional-directive
text-if-true
[else
text-if-false]
endif
  • 文本如果真可以是任何行文字,被视为makefile文件的一部分,如果条件为真。如果条件是假的,没有文字来代替。
  • 中括号[]表示忽略相应内容

一个复杂的条件
语法如下:

conditional-directive
text-if-true
else
text-if-false
endif
  • 如果条件为真时,文本,如果真正的使用,否则,如果假文本来代替。的文本,如果错误的数量可以是任意的文本行。

有条件的指令的语法是相同的,无论是简单或复杂的条件。有四种不同的测试不同条件下的指令
这里是一个表:

ifeq (arg1, arg2)
ifeq 'arg1' 'arg2'
ifeq "arg1" "arg2"
ifeq "arg1" 'arg2'
ifeq 'arg1' "arg2" 

与上述条件相反的指令如下:

ifneq (arg1, arg2)
ifneq 'arg1' 'arg2'
ifneq "arg1" "arg2"
ifneq "arg1" 'arg2'
ifneq 'arg1' "arg2" 

2,条件式指令示例

条件指令,跨平台编译。

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
        $(CC) -o foo $(objects) $(libs_for_gcc)
else
        $(CC) -o foo $(objects) $(normal_libs)
endif

2,include 指令

include指令告诉make暂停读取当前makefile文件和读取一个或多个其它的makefile,然后再继续。该指令是一行在makefile中,看起来像这样:

include filenames...

文件名可以包含shell文件名模式。允许额外的空格开头的行被忽略,但不允许一个标签。例如,如果有三个.mk'文件, a.mk’, b.mk', and c.mk’, 和一个 $(bar) 扩展到bash中,然后下面的表达式。

include foo *.mk $(bar)

is equivalent to

include foo a.mk b.mk c.mk bash

当MAKE处理包括指令,它包含的makefile暂停读取,并从各列出文件中依次读取。当这个过程完成,使读取指令出现在其中的makefile的恢复。

3,override 指令

如果一个变量已经设置的命令参数,在makefile中被忽略的普通任务。如果要设置makefile的变量,即使它被设置的命令参数,可以使用一个override指令,这是一行看起来像这样:

override variable = value

or

override variable := value

4,=、+=、:=、?=

= 直接赋值
:= 忽略掉之前的赋值
+= 追加
?= 如果前面没有定义,则定义生效

九,Makefile文件重新编译

make 程序是一个智能的实用程序和工作根据在源文件中的变化。如果有四个文件main.cpp,hello.cpp,factorial.cpp和functions.h。这里所有reamining文件是依赖functions.h,main.cpp的是依赖于hello.cpp,factorical.cpp。因此,如果做任何改变functions.h然后将重新编译所有源文件来生成新的对象文件。但是,如果做任何改变main.cpp,因为这是不依赖任何其他的过滤,那么在这种情况下,只有main.cpp文件将被重新编译而hellp.cpp factorial.cpp将无法重新编译。

1,重新编译的条件

虽然编译一个文件时,MAKE检查目标文件和比较时间表,如果源文件有更新的时间戳比目标文件新,然后将生成新的对象文件,假设源文件已被改变。

2,避免重新编译

有可能是项目包括成千上万的文件。有时候可能已经改变了一个源文件,但不想重新编译所有依赖于它的文件。例如,假设添加宏到一个头文件或声明,许多其他文件依赖。假设在头文件中的任何变化需要重新编译所有相关文件,但要知道,他们并不需要重新编译,宁可不要浪费时间等待他们的编译。

  • 如果预期改变头文件的问题之前,可以使用-t标志位。这个标志告诉make命令不运行的规则,而是来标记目标,迄今为止,通过改变它的最后修改日期。遵循以下步骤:

    • 1.使用命令’make’来重新编译真的需要重新编译源文件。
    • 2.在头文件中进行更改。
    • 3.使用命令-t来记录所有的目标文件为最新。下一次运行make,在头文件中的变化不会引起任何重新编译。
  • 如果已经改变了头文件的时候,有一些文件就需要重新编译,做到这一点已经太晚了。相反,可以使用-o文件的标志,这标志着一个指定的文件作为old。这意味着该文件本身不会被重制并没有别的其交代将被重制。遵循以下步骤:

    • 1.重新编译源文件,需要编制独立的特定头文件的原因,make -o headerfile。如果涉及几个头文件,使用一个单独的-o选项,每个头文件。
    • 2.touch所有目标文件使用make -t,避免重新编译.

3,touch

touch更新库为最新时间,避免项目由于时间间隔过短不更新问题。
比如:

touch  xxx.cpp

十,Makefile 其他功能

1,make 递归使用

递归使用的手段使用,make在makefile作为命令。这种技术是非常有用的,当你想要的makefile各种子系统组成一个更大的系统。例如,假设你有一个子目录,子目录都有其自己的makefile,并且您希望所在目录的makefile中运行make子目录。可以做到这一点如以下:

#方式一:先切换目录再去make编译
subsystem:
        cd subdir && $(MAKE)

or, equivalently
#方式二:编译时去寻找切换目录 	
subsystem:
        $(MAKE) -C subdir

可以编写递归复制这个例子只是通过make命令,但有很多事情,了解他们是如何和为什么工作的,以及如何子涉及到顶层make。

2,通信变量到子make

顶层make变量的值可以被传递到子通过环境,通过显式请求。这些变数定义子作为默认值,但不会覆盖子的makefile使用makefile中所指定的,除非使用`-e’开关

向下传递,或导出,一个变量,变量和其值的环境中运行每个命令添加。子make反过来,make使用环境变量值来初始化它的表格

特殊变量SHELL和MAKEFLAGS总是导出(除非取消导出)。 MAKEFILES导出,如果把它设置到任何东西。

如果想导出特定变量的一个子制造,使用导出指令,像这样:

export variable ...

如果想阻止一个变量被导出的,使用撤消导出的指令,像这样:

unexport variable ...

3,MAKEFILES 变量

MAKEFILES,环境预定义的宏,如果环境变量的定义,make额外的makefile 名称列表(由空格分隔)之前被读取别人认为其值。这很像include指令:不同的目录中查找这些文件。
MAKEFILES的主要用途是MAKE递归调用之间的通信没有报错,易把项目变得复杂,但没有必要,最好不用定义和使用该变量

LDFLAGS=-lstdc++
all:hello install
	@echo "MAKEFILES=$(MAKEFFILES)"

4,头文件包含在不同的目录

如果已经把你的头文件在不同的目录,在不同的目录中运行make,那么它需要告诉头文件的路径。这是可以做到的makefile中使用-I选项。假设该functions.h文件可在/home/yidaoyun/header头和其他文件/home/yidaoyun/src/然后进行文件将被写入如下。

INCLUDES = -I "/home/yidaoyun/header"
CC = gcc
LIBS =  -lm
CFLAGS = -g -Wall
OBJ =  main.o factorial.o hello.o

hello: ${
    
    OBJ}
   ${
    
    CC} ${
    
    CFLAGS} ${
    
    INCLUDES} -o $@ ${
    
    OBJS} ${
    
    LIBS}
.o:.cpp
   ${
    
    CC} ${
    
    CFLAGS} ${
    
    INCLUDES} -c $<

5,追加更多的文本变量

通常,它用于添加更多的文字,已定义的变量的值。
make 这行包含+=,像这样:

objects += another.o

这需要值的变量对象,并添加文字another.o(前面由一个单一的空间)。因此:

objects = main.o hello.o factorial.o
objects += another.o

设置文件main.o hello.o factorial.o another.o的对象。
使用+=是类似于:

objects = main.o hello.o factorial.o
objects := $(objects) another.o

6,Makefile中的续行

如果不喜欢太长的行,在Makefile中,然后你可以使用反斜杠\但反斜杠后面不能有任何字符(空格,tab建,字母,下划线等),如下所示

OBJ =  main.o factorial.o \
	hello.o

is equivalent to

OBJ =  main.o factorial.o hello.o

7,从命令提示符下运行的Makefile

如果已经准备好Makefile的名称为“Makefile”文件,然后简单地写在命令提示符下,它将运行Makefile文件。但是,如果有任何其他的名字的Makefile,然后使用以下命令

make -f your-makefile-name

十一,Makefile案例

这是一个例子编译hello程序Makefile。此程序包含三个文件main.cpp,factorial.cpp,hello.cpp。

# Define required macros here
SHELL = /bin/sh

OBJS =  main.o factorial.o hello.o
#CFLAG编译参数,-Wall:开启所有警告,-g:开启调试信息
CFLAG = -Wall -g
CC = gcc
#INCLUDES:依赖文件,没有就空,-I:当前文件
INCLUDES = -I .
#LIBS 依赖库
LIBS = -lstdc++

hello:${
    
    OBJS}
	${
    
    CC} ${
    
    CFLAGS} -o $@ $^ ${
    
    LIBS}

clean:
	-rm -f *.o hello

.o:.cpp
	${
    
    CC} ${
    
    CFLAGS} ${
    
    INCLUDES} -c $<
  • 建立hello 程序使用“make”hello项目。如果发出命令“make clean”,则它会删除所有的对象可在当前目录中的生成文件。

总结

1,vim命令模式下快捷键:
u:撤销最后保存前的所有输入。
control+r:恢复 最后保存前的所有输入。
按下两次d:删除某行。

猜你喜欢

转载自blog.csdn.net/MOON_YZM/article/details/131098875
今日推荐