|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
我们在 Windows 下使用 C 语言开发的时候,一般都会有支持 Windows 的开发工具,比如我们学习 51 单
7 S0 S L8 U+ V" z* Y% D0 G片机或者 STM32,所使用的的 Keil 开发软件。此类开发工具一般都会集编辑、编译于一体,我们只需要编辑
; d$ Z5 q" ]: h. e6 a8 [1 O+ L$ u好代码,电机开发工具的编译按钮,就可以自动为我们编译出可执行的二进制文件了。Ubuntu 下的 C 语言3 R- R9 p4 _2 N! g+ J4 {: y! K# V
开发与 Windows 下的 C 语言开发是不一样的,并没有图形界面的开发工具,而且编辑和编译是分开的。我" ], T' u, y9 v6 F; B* I4 l
们需要使用文本编辑软件先编写代码,然后使用编译工具(GCC)来编译,最终生成可执行的二进制文件。
$ J- O; v# x, N如果我们的工程中有多个源文件,在编译的时候我们通常会通过 Makefile 文件来管理整个工程的源文件。
! l0 u5 y8 R$ H8 `本章我们来学习如何在 Ubuntu 下进行 C 语言成的编辑,GCC 编译,Makefile 文件的使用。通过本章学习我0 D" V, r4 w: F" ~8 O' C
们可以掌握 Linux 下的 C 语言开发基本方法,为后面的学习做好准备。
) F( [5 \: W) h- I3.1 x Linux 下编写第一个 C C 程序 程序
8 [6 P" V/ d) v4 s0 @本章节开始的部分我们介绍了 Ubuntu 下 C 程序开发分成两部分:编辑和编译。Ubuntu 下有很多种文本
R" P8 f6 J9 S1 a" z" _编辑的工具,如 vim、Gedit、Emacs,这里我们使用前面章节介绍的 vim 文本编辑工具来编辑 C 程序。相信
5 C" w9 I1 E3 J# T大家在学习 C 语言的时候都是从“Hello World”这个程序开始的吧,下面我们通过这个程序,来学习下 Linux
U, f" `. g; w/ [5 j) E" N1 T8 S下 C 程序开发的流程。2 u( ` G/ L: D
3.1.1 代码编写9 g) _ F& h3 w/ u
首先我们在用户根目录下建立文件夹“work”,用于保存所有 C 语言的程序,运行结果如下图所示:' p2 u9 b7 q/ x8 b! I
3 L1 g8 y' `1 T; ^3 k然后进入创建的文件夹 work,为了方便管理,我们每个例程都创建单独的文件夹,首先我们创建文件
/ f% w4 O' Z/ j夹“hello_world”来保存我们的第一个 C 程序,如下图所示:
2 P, P, R, @8 j9 x
9 L8 h9 S, o0 ?2 _1 Z
然后进入上图中的 hello_world 文件夹,使用 vi 命令新建文件“main.c”,然后在里面输入下面的代
9 w5 u$ A" Z z( j码:
, }; X! G; k% q& Y# A$ } V* w#include # o1 A- X0 C& I' ?
int mian(int argc, char *argv[])1 `! Y. a8 \* W- D. O C7 w: ?
{
, a( N" R5 B* m0 W8 k5 \printf("Hello World!\n");8 u' n6 D8 L7 f! D% C
return 0;
0 ?( Z3 p. I, y$ o- N2 {; J}' v* [ O, Y3 N; X& K" K
编写完以后保存并退出 vim 编辑器,然后可以使用 cat 命令查看代码是否保存成功,如下图所示:
( u7 f# Z) r+ i; ^! N J' F5 |7 A
/ X- n7 _% E+ ~& V通过上图可以看到代码已经编辑完成了。
/ S7 u! k7 X8 ~6 W2 \; M0 H3.1.2 代码编译
- H8 V' w+ y1 S6 O& KUbuntu 下使用 gcc 编译器来编译 C 程序,我们在安装 Ubuntu 系统的时候,gcc 编译器 morning 安装好+ ]; R2 ~: I( l5 |
了,我们可以在终端输入“gcc -v”来查看下 gcc 的版本,如下图所示: W$ @8 }. H& H! Q) s/ U
( ]: `- F& l, `! J5 n& t' }
通过上图可以看到 gcc 的版本信息,说明我们的 Ubuntu 上已经安装了 gcc 编译器了,下面我们来看看* a. T5 h2 m! I3 y# K; ^2 D
怎么通过 gcc 编译我们的第一个 C 程序,我们在终端输入“gcc main.c -o main”,然后回车,会生成 main 文4 T( I! b' z; ?8 i
件,如下图所示:7 G% F% t/ p* e: U( G9 V' m) c) s/ f( z; h
8 [, ^( }; u0 _/ t# ]8 A9 i在上面的“gcc main.c -o main”这个命令里面“-o“用来指定编译生成的文件名字,我们指定的是 main,' O9 X' a6 D; Q
所以在图 3.1.2.2 中可以看到生成了文件“main”,如果我们想生成其它的文件名字,我们只需要修改“-o”7 }% p' q& j/ n7 ]% v
后面的 main(修改成您希望生成的文件名字)。经过前面的步骤已经生成了可执行文件 main,现在我们演
1 @3 s1 Y, u" K- `4 V1 x1 m2 B示下在终端如何运行一个可行性的程序,我们直接在终端当前目录下(可执行文件所在的目录下)输入
6 v8 v% J, @0 m% A, d) E1 U% B“./main”,然后回车,就可以运行可执行文件 main 了,运行结果如下图所示:
5 b1 [7 |; D6 ^8 A# w1 i: ~9 m x
0 j* h! A' t2 \8 I, h
在上图中运行的命令“./main”,其中的“./”代表当前目录下。我们可以看到在终端打印出了 Hello
5 z+ L; |3 } r6 n( a" X: SWorld!。至此,Linux 下的 C 语言编辑和编译的一整套流程我们就介绍完了。
0 c' q) z- |! x% M0 x3.2 c gcc 编译器 编译器 v7 \) ^2 r5 S8 L( Y# c
3.2.1 gcc 命令分析
) e* }. q+ R, t, X在 3.1 节我们已经使用 gcc 编译了 Linux 下的第一个 C 程序,gcc 命令的格式如下:% v; f# }- c* U! @
gcc [参数] [文件名]
5 y- A/ }5 y# K% H( l0 w0 K主要参数说明如下:6 w- y, O) _7 _6 f- x) d1 D1 l
-c 编译、汇编到目标代码(.o),不链接成可执行文件/ U' Y6 X& N) ?+ o
-g 生成调试信息# y1 \7 I$ I, U* |/ y
-o 编译完成后生成的文件名,如果不使用该选项,默认生成 a.out 文件
- |+ h! y8 L H, E4 L-O 对程序进行优化编译,产生的可执行文件执行效率高6 M$ i3 p, g& X( S" G
-w 不生成任何警告: Q* {4 _ C0 k: C( j
-S 仅编译到汇编语言,不进行汇编和链接% P5 v4 e8 k; O; e
3.2.2 编译警告错误处理& V, v& ?) a$ _- d, C: z
我们是 Windows 下使用 Keil 或者其他开发工具,在编译的时候,如果程序有错误,开发工具会提示出
; J) x- ^9 j9 D# S* O# |3 e6 Z, c具体的错误信息,可以很方便的定位到问题点,快速的修改出现的问题,gcc 同样也有类似的功能,下面我
q( p/ A; Z* s( @) w们来看下 gcc 的错误提示功能,首先我们在 work 目录下建立文件夹“test2”,然后使用 vim 在 test2 文件% Q5 q7 t* M8 n7 M$ O" P
夹创建 main.c 文件夹,在 main.c 文件输入如下代码:$ g% t: w; |: Z' o
#include
3 A' I2 X& O, c" l' u7 g3 rint main(int argc, char *argv[])4 M' |3 c- d& R1 F. i
{4 T$ u6 U. o( ^- i
int a;) C$ V6 }3 M& P! `5 m
a = 1
7 ~! S. k2 G1 o" n* {# Jprintf("a=\n", a);
; t' P% k8 Y# ]2 ~% Zreturn 0;/ {5 b5 s5 I' O9 j) ]' H
}
: A) F/ V& r9 W0 t1 [上面代码有两处错误:$ w" L' m* J9 J8 m( }
第 7 行 最后缺少“;”; U/ k# I( p+ S5 h7 O: f9 |4 H
第 9 行 printf 语法不对,应该为:printf("a =%d\n", a); \; C! G0 X2 k. J& Y; C6 T j
我们使用 gcc 编译 main.c,可以看到 gcc 会提示错误信息,如下图所示:
) i" }8 y- e& A/ s5 I
$ P+ C" \" j+ b( g0 b
从上图中可以看到在 mian.c 文件的第 9 行 printf 前面缺少“;”我们在第 7 行“a = 1”后面加上“;”,0 G1 Q# h" V( s
然后继续编译,如下图所示:' S6 Q4 B* m; V
' {; s+ @" _, i! \1 {从上图可以看出编译提示语法格式不对,我们把第 9 行修改成“printf("a=%d\n", a);”,然后在继续编$ T2 r9 ^ \ V2 r$ q0 H
译,如下图所示:
( H. S9 U* g8 o7 j1 T' \4 _
2 P/ Y' X3 n1 V! y4 Z' ?
我们可以看到这次 gcc 编译通过,最终生成了文件 main。我们在终端执行 main,运行结果如下图所示:
, i: d) o% ]6 u- |: Y/ p) j; Q% [( X# Q
) c- z a4 s2 k; T从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出7 O) g4 i) w' C9 j
程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。
4 g6 }: U" U; B4 P2 g9 \: C2 u从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出$ U* s- n& P5 z4 W5 f& j% k
程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。 f- U( ^6 g& P
3.2.3 gcc 编译流程; Y7 n6 K C0 K0 I
gcc 的编译流程可以分成四个步骤:4 v+ v7 G% M: u' h4 d' s
1.预处理,生成预编译文件(.文件)
: e* X* W9 U) ]/ \) b3 _# g2.编译,生成汇编代码(.S 文件)
( q+ {4 e3 l# x* a% T. d% ]/ d3.汇编,生成目标文件(.o 文件)
4 K" ]: ^) K1 L/ S4.链接,生成可执行文件
7 { \- S3 Y; O; W# l1 J- O3 W3 3.3 初识 Makefile7 x" ]; g3 w! L3 }6 ?3 e
3.3.1 什么是 Makefile
8 M* v1 M) ~( \3 p& _# R在 3.2 章节我们了解了在 Ubuntu 系统下通过 gcc 编译器来编译 C 程序,在我们演示的历程中只有一个
/ T& k# q m4 d9 T" LC 文件,我们直接在终端输入 gcc 的编译命令,就完成了 C 程序的编译。我们在实际开发过程中,如果我们
6 k9 l$ p) m, c- \6 o1 `* D0 o) v的工程有几十个,或者几百几千个 C 文件,我们通过在终端输入 gcc 命令来编译,这显然是不现实的。为 V1 E7 \7 T( ~$ S( n
了解决这个问题我们可以使用“make”命令,它会解析 Makefile 文件中的指令(应该说是规则)来编译整9 S4 g3 g3 h( v: S
个工程。在 Makefile 文件中描述了整个工程的所有文件的编译顺序,编译规则。
; u8 _/ a; S7 X: O3 T' C作为一个专业的程序员,一定要掌握 Makefile 的,我们可以通过 Makefile 能了解到整个工程的处理过程0 x& l% f" ]* \% M
的。; \( F/ f$ g- f8 d' l
由于Makefile涉及到很多的知识点,以至于可以单独写本书来讲述,所以本章我们只是讲解下Makefile
* k% b# l+ j% D; \. ?9 X! U) R的基础入门,如果详细的研究 Makefile,可以给大家推荐《跟我一起写 Makefile》这个电子文档,该文档
8 ~& G0 l+ H. t# t6 g已经放在了:i.MX6UL 终结者光盘资料\09_其它参考资料里面了。+ \5 a+ W- S# w& o1 c3 A
3.3.2 第一个 Makefile6 d, V$ k) |1 u: V
在本节我们建立这样一个工程,计算两个整形数的和,并将结果在终端显示出来。在这个工程中一共有2 F, t+ B& w+ s- F s8 g: J
main.c、calc.c 两个 C 文件和 calc.h 这个头文件。其中 main.c 是主文件,calc.c 负责接收 main.c 传过5 p" }* g" f1 g: c! P$ X% B+ g M
来的数据,然后进行相加。main.c 文件的内用如下:; I9 M1 _- G9 U
#include
* S. H+ L- G. F6 F3 I0 Yinclude "calc.h"& v: X6 _% Z) \( E- I2 K5 c3 z
int main(int argc, char *argv[])
7 ?5 S& v- c5 I5 X) d* {{
6 c% A, I3 Y R3 j' a M0 d/ Uint a = 3, b = 6, sum;) i0 M" [& A! {2 C) m
sum = calc(a, b);
! g0 H( U1 @* P$ u: u+ C- P2 }- V: Hprintf("%d + %d = %d\n", a, b, sum);
5 ]% A" S. S( ?+ @/ xreturn 0;
7 y4 F- c; V" W9 b% _4 {5 \! s}! \9 I/ {. M. b- d+ d% j1 a7 V
calc.c 文件内容如下:
1 ]8 a; ~6 {' E7 m$ i' K1 l#include
0 N% o2 M" C. H: |# ~* x7 @int calc(int a, ing b)
* d% D0 J2 k2 U: R6 I{: Y) X- K/ m. M/ T/ F8 p, t
return (a+b);* q" B# e# a# ?/ C
}
5 V/ @6 q5 p5 b ~, v文件 calc.h 内容如下:
' v; K ~: o+ ]% w/ l#ifndef _CALC_H
( z! z" ~! R3 A+ y) f( }#define _CALC_H) O/ p6 b! r4 \' T3 q$ P
int calc(int a, int b);& n5 p ], D5 M3 b) z
#endif; z3 D; M. S: `# m
上面就是我们这个工程的所有源文件,我们在终端使用 gcc 编译这个工程,在终端输入“gcc main.c
3 t: \5 H1 d7 |, a+ p) ucalc.c -o main”,该命令的意思是用 gcc 对 main.c、calc.c 进行编译,然后输出可执行文件 main,运行# z, f+ M _7 p- i& N
结果如下图所示:7 I$ o8 n7 S; b* S$ x `
7 C- J6 D6 F7 ?( o( V; T9 W通过上图可以看到生成了可执行文件 main,我们在终端运行 main 执行文件,运行结果如下图所示:4 W! q. O, [. U4 L3 C
3 f% i9 v7 }' t+ b. C9 x我们可以看到上图的运行结果和我们设计的结果是一致的。由于我们的这个工程是有三个文件,如果0 a& X2 y' D( e: ~
工程有几百个,几千个的文件,或者如果有一个文件被修改,使用上面的命令将会编译所有的文件,如果* ^3 c0 ?# x* Q; j: b
我们的工程有上万个文件,编译完一次工程所需要的时间就很可怕。最优的方法就是编译过一次以后,如! B- D0 Q' B/ K+ t9 D. `
果后面在编译,只编译修改的文件,这样就会节约很多时间,因此我们修改下编译方法,命令如下:
a' D3 ^8 V9 S7 d4 Dgcc -c main.c$ e d t j$ s! [# D P8 C
gcc -c calc.c
- V/ T1 l+ p5 t% }; A Ngcc main.o calc.o -o main; m) |; R5 ^/ f9 t
我们在终端输入上面的命令,结果如下图所示:
6 A/ F- v) }( ?. y9 ^
9 C. k! _: J' P1 F* b, c
上图的第一条和第二条命令里面使用了参数“-c”是把 main.c 和 calc.c 编译成对应的.o 文件,最后
7 W5 {* T) H" X一条命令是把编译生成的.o 文件链接成可执行文件 main。假如我们修改了 main.c 这个文件。只需将 main.c, t9 |4 d$ ?; ?3 C$ Q$ V
这个一个文件重新编译下,然后在把所有的.o 文件重新链接成可执行文件,对应的命令如下:' i# Z# H2 f/ r& V
gcc -c main.c' q' z8 F( X$ E: @8 c4 w6 a
gcc main.o calc.o -o main
L& [! q# |4 K; `可是这样还有一个问题,如果需要修改的文件有很多,这样工作量也会不小,所以我们需要一个工具:7 K# P# U9 C) P; h* ~
1.如果工程没有编译过,就会把工程中的.c 文件全部编译并连接成可执行文件
" t# S/ Z1 I+ _' B3 t- |0 f2.如果工程中有某些文件修改了,只编译修改的文件并连接成可执行文件/ E' w6 l$ H1 m( n: q# _/ F4 x4 D
3.如果工程中的头文件修改了,那么就要编译所有引用这个头文件的.c 文件,并且连 接成可执5 L2 H% Q7 b$ _' s6 R; M. k1 ^
行文件! \/ R( a% ~$ r7 b' H0 e
我们开头说的 Makefile 就是完成这个功能的,下面我们在工程中建立一个 Makefile 文件来实现这样的功
: @9 v2 I: q9 o5 |5 [5 o能(注意:文件名字必须为 Makefile,大小写是区分的)。我们使用 vim 创建 Makefile 文件(Makefile% R5 ~ G0 H- ^( v' A( @
和我们的 main.c、calc.c 在同一级目录下),然后输入下面的脚本:* b( W) U I- x) b
main:main.o calc.o, _1 t# G1 c4 K s- |$ x2 M& e
gcc -o main main.o calc.o$ ]( d% p# @2 P; u5 d5 K! B
main.o:main.c' j+ Y. n1 m9 }, h+ B
gcc -c main.c: m5 E0 l# o5 f9 S: E+ L5 f Y4 ?; y8 s
calc.o:calc.c
/ S" r" z2 R. Egcc -c calc.c
$ `! p; F, I. a% H5 i0 Bclean:
4 b$ ?+ e ^" w6 r Z* N$ q( p& Frm -RF *.o
, u9 P. c. T, o! f# H4 grm -rf main" H1 k% r: [& P! C
上面脚本缩进的行需要使用“Tab”键缩进,不要使用空格,这是 Makefile 的语法要求,编写完成的脚本- }5 `3 A8 |6 @' g) ?$ @* D5 j
如下图所示:5 H& _3 E/ e! y K `
J! @ W, O! ~7 m4 q+ R8 V# C9 L编写好 Makefile,保存并退出,然后我们在终端输入“make”命令来编译我们的工程,make 命令会在3 j5 o P1 i$ A7 r# n( t
当前目录下查找“Makefile”文件,如果存在的话就按照 Makefile 里面的规则进行编译,如下图所示:9 Y; {' L! c7 _0 Q
/ Z0 T3 E. w3 \5 _4 @* |7 E8 E
通过上图可以看到编译产生了 main.o、calc.o 和 main 执行文件,说明编译成功了。接下来我们修改4 |! _5 P2 C4 Z' G$ H! M' ]
下 main.c 这个文件,如下图所示:. U, [5 g3 b4 r5 a6 }' Z/ Y5 ^
Q5 N3 o7 Z/ @: n0 j. i
然后保存并退出,然后在终端输入“make”再次编译下工程,如下图所示:0 q3 m# F( e0 j* t" [/ U7 T* R& d
0 d+ C- X) d; ?/ X2 ^. @$ H" B9 F通过上图我们可以看到只重新编译了修改的 main.c,并最终重新链接生成可执行文件 main,我们在终; S3 f) [6 i3 X1 d) ?0 A
端运行可执行文件 main,如下图所示:4 p# `& m( ~3 u
2 a9 u, I. J# {7 u+ Z" D- B# L
从上图的运行结果可以看到最后的结果等于 10 了,和我们程序的设计结果是一样的。& k) C" a) a6 X/ i; U
3.4 e Makefile 语法 语法
. m% ?4 E$ S+ G" ^! k' {0 ]1 i3.4.1 初识 Makefile
. k# O1 a' H$ K, K- E( ~5 m3 EMakefile 文件是由一些列的规则组合而成的,格式如下:( R( B8 o3 k8 Y/ o
target(目标文件) ...: prerequisites(依赖的文件) ...
7 t% m$ H) i: z6 }1 [command(命令)
# j* F: B% J+ t6 M& H...
4 L8 Y1 Y6 I% i. ~% P9 h% S, w6 c O.../ s- [5 y9 f4 P# {' |- r
比如 3.3.2 中写的 Makefile 的规则:
2 M9 O* `7 y0 Y* Umain.o:main.c
9 j3 o' o) [2 {" pgcc -c main.c/ M/ I! Z( e; E
这条规则的 main.o 是目标文件(将要生成的文件),main.c 是依赖的文件(生成 main.o 需要的文件),8 @' K" t! [" W2 P
“gcc -c main.c”是生成 main.o 需要运行的命令。e Makefile 中每行的脚本如果有缩进的情况,必须使用) V' ~" p: ~8 V; g& E
“ Tab ” 键缩进,切记不能使用空格缩进(这是 e Makefile 的语法要求),大家一定要切记!3 K% ~8 D# s5 z9 _$ I1 l$ v* G9 z
下面我们来分析一下图 3.3.2 章节中写的 Makefile 文件,脚本如下:' M3 K, p: v2 |( a8 y2 L; C, q
1 1 main:main.o calc.o
: Q; w- t8 K* Y1 P; ~2 2 gcc -o main main.o calc.o( R! Y& L( R$ O7 M
3 3 main.o:main.c9 v9 J5 w6 p- K# K
4 4 gcc -c main.c
$ G7 _3 k/ M0 O( l# V5 5 calc.o:calc.c& J! G* a* K! j- E
6 6 gcc -c calc.c6 I T) M# `* @
7 7
: j5 T" z+ I2 ?' A; j$ |8 8 clean:8 i! g8 p5 i9 ^6 [+ n
9 9 rm -rf *.o1 p- |9 a# i' ~, u/ ]' I# u+ s, G
10 rm -rf main9 t3 |0 O/ {+ f! L8 B) e0 U2 R
该脚本一共有 4 条规则,1、2 行是第一条规则,3、4 行是第二条规则,5、6 是第三条规则 8、9、100 K% W; C4 S9 A# l$ V, I6 N+ f
是第四条规则。我们在运行 make 命令的时候,会解析当前目录下的这个 Makefile 文件里面的规则,首先3 ~* v9 h1 U7 [; i2 y8 k" x
解析第一条规则,第一条规则中的目标文件是 main,只要完成了该目标文件的更新,整个 Makefile 的功能
6 S! m/ N3 m/ _4 _3 w* f, r就完成了。在第一次编译的时候,由于目标文件 main 不存在,则会解析第一条规则,第一条规则依赖文件
V5 @4 W7 r, `- [: {main.o、calc.o,make 命令会检查当前目录下是否有这两个.o 文件,经过检查发现没有,然后 make 会在( P! |- e6 M3 A7 v, T
Makefile 中查找分别以 main.o、calc.o 为目标的规则(第二条,第三条规则)。执行第二条规则依赖的文
6 L2 A+ x# v' X- A- t% H! Q! A7 R& x件是 main.c,make 命令检查发现当前目录下有这个文件,然后执行第二条规则的命令“gcc -c main.c”
( F+ p2 ~ {+ b# \生成 main.o 文件。然后执行第三条规则,第三条规则的目标文件是 calc.o,依赖的文件是 calc.c,make
; f- W o& S; G命令检查发现当前目录下存在该文件,然后执行第三条规则的命令“gcc -c calc.c”生成 calc.o 文件,. h% \( L" e! j( F% d- R- E
至此第一条规则依赖的 main.o、calc.o;两个文件已经生成了,然后运行第一条规则的命令“gcc -o main0 o. Z- _' ]$ r2 V& `, n
main.o calc.o”生成 main 文件。因为 make 命令运行的时候会从 Makefile 的第一条规则开始解析,然后
( h7 }7 h- m( u- f2 ^% w根据第一条规则的依赖文件去遍历文件中的“对应规则”,然后在根据“对应规则”的依赖文件去遍历“对
! Z7 T3 H6 q2 \( r* i, a应的规则”,采用这样递归的方式会遍历出完成第一条规则所需要的所有规则。下面我们来看看第四条规
! l" e5 J0 m" b4 M则的目标文件是 clean,我们通过查看发现该规则与第一条规则没有关联,所以我们在运行 make 命令的时1 ~6 ?8 k$ `: t5 P1 O6 G
候,不会遍历到该规则。我们可以在终端输入“make clean”命令来运行第四条规则,第四条规则没有依! g/ A' b3 {9 r) r2 R& |: B
赖的文件,所以执行运行命令“rm -rf *.o”和“rm -rf main”,这两条命令的功能是删除以.o 问结尾的
( g7 |- J7 T+ W! s7 E% D, Y, v k所有文件,删除文件 main,运行如下图所示:
2 R; r. a5 N- O& Z
- E2 g' z0 w" P1 v( u- w8 B通过上图可以看到 main.o、mcalc.o 和 main 三个文件已经删除了。通过该规则我们可以清除编译产生
' L/ d) w9 u: ]; }的文件,实现工程的清理。9 E3 r4 s! x% c' v& `
我们再来总结一下 make 命令的执行过程:
# M' l7 q* k9 D1.make 命令会在当前目录下查找以 Makefile 命名的文件1 p0 G! q- C- D* M7 B& T0 E( f
2.找到 Makefile 文件,就会按照 Makefile 里面的规则去编译,并生成最终文件
% G7 R8 x( L C; F; p3.当发现目标文件不存在或者所依赖的文件比目标文件新(修改时间),就会执行规则对应的命令来更新。( w% v& T/ h3 c
我们可以看到 make 是一个工具,他会通过 Makefile 文件里面的内容来执行具体的编译过程。1 O. p, ^8 Y2 F/ [, C0 U6 j' I! e
3.4.2 Makefile 的变量
8 r5 e& J* r5 Y/ O在 3.3.2 章节中的 Makefile 第一条规则:
. g8 q9 t; R+ K" Lmain:main.o calc.o
' v6 E$ F! |* c7 g. M8 ]gcc -o main main.o calc.o
# j$ w I) z3 G5 }% A5 Q在该规则中 main.o、calc.o 这两个文件我们输入了两次,由于我们的额 Makefile 文件内容比较少," O# f. b; C! s4 E3 p) {
如果 Makefile 复杂的情况下,这种重复的输入就会非常占用时间,而且修改起来也会很麻烦,为了解决这
: A4 u8 v+ M3 z+ `. a7 s" G个问题,Makefile 可以使用变量。Makefile 的变量是一个字符串。比如上面的规则我们声明一个变量,叫1 c a3 }' o3 Z0 n$ j* y
objects,objs 或者是 OBJ,反正不管是什么,只要能够表示 main.o、calc.o 就行了,我们修改上面的规7 b4 x" C S" y: t8 D
则
$ P5 ~4 t3 _3 H; a8 U1 1 objects = main.o calc.o
3 Y/ Y v$ g+ V2 2 main ( objects)- ^% K3 y. c% d) P8 M
3 3 gcc -o main $( objects)
3 w2 |/ v3 W$ j, S我们来分析下修改后的规则,首先第一行是我们定义了一个变量 objects,并给赋值“main.o calc.o”,) |: \. `5 u- h7 v5 P5 j% B
第二行、第三行用到了变量 objects。Makefile 中的变量引用方式是“$(变量名)”,变量 objects 的赋值
0 ~# V9 e- W5 Y1 B( v使用“=”,Makefile 中变量的赋值还可以使用“:=”、“?=”、“+=”,这四种赋值的区别如下:
4 T# |7 C8 a; N8 y, E- S1. “= = ” 赋值符
& `. F9 v/ ?: m+ P# O8 ^$ h s我们先在用户根目录的 work 目录下创建一个 Makefile 脚本,输入下面的内容:- ?' R8 S1 v( E. Z. V! }' e9 D
1 ceshi1 = test n7 }% `2 @1 A& v6 R
2 ceshi2 = $(ceshi1)4 H: U! ~0 t0 F* y6 D
3 ceshi1 = temp; j" P% d0 J! L6 c: @( z
4 u" W& R* a& ~9 |# s, J
5 out:
3 G4 c0 [4 X# N$ I6 m) h6 @Echo ceshi2 (ceshi2)
! @( `8 Q; ^( X7 r/ v第一行我们定义了变量并赋值“test”,第二行定义了变量 ceshi2 并赋值变量 ceshi1,第三行修改变量
$ ~! T4 G1 m8 yceshi1 的值为“temp”,第五行、第六行是输出变量 ceshi2 的值。我们在终端输入“make out”命令,如 b' [& ~7 d, f: I9 p5 c
下图所示:
& F' D C$ P' L$ |* [ q
" x' X9 N% \- D& j
在上图可以看到变量 ceshi2 的值是 temp,也就是变量 ceshi1 最后一次的赋值。* o4 x& S* b+ R$ |
2. “ := ” 赋值符
2 K" }. y& n* x5 B4 }( h3 {我们修改“=”赋值符中的代码,第二行的“=”改成“:=”,代码如下:
6 ?# y- x+ j3 g6 M( M5 O( H9 W/ g2 w1 ceshi1 = test
' I3 g, t: j' ^6 q4 X( }0 x2 ceshi2 := $(ceshi1)7 I2 }+ P$ M1 }4 c7 b1 n. T4 \
3 ceshi1 = temp
" g( y$ W: j; r9 G& g43 F- H1 Q$ X* i4 j
5 out:
$ V2 N+ w0 p8 u) u" Z, Q6 @echo ceshi2 (ceshi2)
$ L4 \8 h* y6 O0 t$ ?( V1 R我们在终端输入“make out”命令,如下图所示:
; U) t+ W1 s& K+ F: w
7 \/ s: u2 x8 y# i6 b
我们可以看到上图的运行结果输出变量 ceshi2 的值是 test,虽然在第三行我们修改了变量 ceshi1 的( o) r' V1 \+ ?. o$ [% E* n
值,通过本实验我们可以看到“:=”赋值符的功能了。
; Z4 }! ~7 {# g. a3. “ ?= ” 赋值符
z$ x3 y6 t# n' ^3 Eceshi ?= test
' _) h8 k: l2 r0 u( A$ O“?=”赋值符的作用是如果前面没有给变量 ceshi 赋值,那么变量就赋值“test”,如果前面已经赋值了,. D( E7 d% _' v" Y% x: {+ G
就使用前面的赋值。/ p' X8 ?( I1 O7 l# U2 h; D
4. “ += ” 赋值符
% Y9 N& D+ @. hobjs = main.o: l M- _+ z- S$ }' U
objs += calc.o
1 C% |" M5 m/ F6 i$ ~" w2 i1 y上面的脚本最后变量 objs 的值是“main.o calc.o”,“+=”赋值符的功能是实现变量的追加。! U* z; i0 @/ U' ^
3.4.3 条件判断9 {# v8 a, K4 ?3 y, c- J
使用条件判断,可以让 make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量
# y1 T F0 s* ]- i的值,或是比较变量和常量的值。其语法有下面两种:
0 @+ p/ F' b$ i1 R4 X7 r% ~! o1.
5 f6 H0 {7 ]. ]! ^$ O$ b1 G9 B3 H<条件比较>" d( V- u6 P( X
[条件为真时执行的脚本]- V n: w8 q% n0 t/ ]. ?7 i
endif
; M. T% [9 C2 j b/ @5 n' {6 q2.& o0 A, s6 W& q* [+ c- P. d. v
<条件比较>
; G7 s- A6 {% G8 {4 j8 X[条件为真时执行的脚本]
3 ]* G5 X. a( k7 x% K! z7 celse' l" A' p2 v! s# _% k/ r* K. C
[条件为假时执行的脚本]0 i; C# R; ]! X$ p1 v2 }4 X
endif8 m& {& V4 F2 p1 o
条件比较用到的比较关键字有:ifeq、ifneq、ifdef、ifndef。6 D! L2 s7 M& S5 v; ^, P
ifeq 表示如果比较相等,语法如下: a6 C( R6 [: k2 O2 d) z; c1 U! P
ifeq(<参数 1>, <参数 2>)* ]" \4 Q. `+ p/ V+ V8 P8 m
ifneq 表示如果不相等,语法如下:
: f5 @6 {5 m$ h) i% r" bifneq(<参数 1>, <参数 2>)$ u; J4 D/ H! ~7 n) @6 D! s T
ifdef 表示如果定义了变量,语法如下:
$ r" {1 G3 d+ V- oifdef <变量名>/ j$ r+ d1 V, d
ifndef 表示如果没有定义变量,语法如下:5 X) l3 v3 J/ F0 i& J$ U {. U w
ifndef <变量名>
9 S3 s7 R1 D3 {% L+ T8 {+ Q# x3.4.4 使用函数4 P0 J. T4 o+ k1 O; ]! O2 y& r
在 Makefile 中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make 所
" P$ i$ c! j' y4 D9 o支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。7 |9 G1 h( \/ \3 e" q
函数的调用很像变量的使用,也是以“$”来标识的,语法如下:
4 L' u0 l9 z3 h V$(<函数名> <参数集合>)
7 p3 [! ?! X8 x或者:& d4 Z$ b' p2 d" C
${<函数名> <参数集合>}
0 N# [% S: X3 X. l2 }# F5 l函数名和参数集合之间以空格分隔,参数集合的参数通过逗号分隔。函数调用以“$”开头,以圆括号或花
5 z* k2 U& e6 R5 t5 |( d括号把函数名和参数括起。感觉很像一个变量。函数中的参数可以使用变量。为了风格的统一,函数和变
5 s/ P, l# r6 R9 s3 I$ |量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。* a- _8 M0 L% X! L6 x2 L6 L
因为统一会更清楚,也会减少一些不必要的麻烦。) U/ v8 V0 \3 J
接下来我们介绍几个常用的函数,其它的函数可以参考文档《跟我一起写 Makefile》。
# {+ t6 Z) [$ }6 Ft 1.subst 函数& X3 {+ W2 w( Y, l$ y; o
$(subst ,,)
; Q8 _+ L# s; q+ R! \5 Z1 C, b. G此函数的功能是把字串中的字符串替换成,函数返回被替换过后的字符串。如下示例:7 [0 G. q; O v
$(subst ee,EE,feet on the street), }2 G/ f& f) O+ f2 o
以上脚本实现把字符串“feet on the street”中的“ee”字符串替换成“EE”字符串,替换后的字符串
" K0 O/ l+ x# E- ?) G3 ^为“feet on the strEEt”。- G8 a U) _$ \# v: L
. 2. t patsubst 函数
# B; U! Z. @& _2 X1 a$(patsubst ,,)
0 ]) i, w* S: Z' L- w, ~- m. [此函数的功能是查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式/ Z3 N' ^% P( O! q0 H% p
,如果匹配的话,则以替换。这里可以包括通配符“%”,表示任意长
8 r5 [# R1 Z1 Y0 D J0 D度的字串。如果中也包含“%”,那么中的这个“%”将是中的那个
9 ^; a) J' u% {“%”所代表的字串。(可以用“\”来转义,以“\%” 来表示真实含义的“%”字符)。函数返回被替换5 h5 q( G! x$ M0 I0 b4 t
过后的字符串。如下示例:5 S1 E3 T6 j( }3 P% d$ E
$(patsubst %.c,%.o,x.c bar.c)8 J6 v9 V, Q8 m B' t
以上脚本实现把字串“x.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.o bar.o”
- E, }. _8 b9 Xp 3.strip 函数5 |7 f+ @# M0 \$ q
$(strip )9 G- P7 H$ t. J V& @3 k, R
此函数的功能是去掉字串中开头和结尾的空字符,函数返回被去掉空格的字符串值。如下示例:. |' p: g2 D& Z9 e' {, ~2 t1 `
$(strip a b c )& L3 O' } b- {! J; ]1 g
以上脚本实现把字串“a b c ”去掉开头和结尾的空格,结果是“a b c”。$ A0 k* }" d& [2 S# f8 C7 Y
. 4. g findstring 函数
" C+ M) n' g2 @# f" }% \$(findstring ,)
- }! `( u: ?' I$ s8 ^此函数的功能是在字串中查找字串,如果找到,那么返回,否则返回空字符串,如下示
* n6 i& J0 d) `" ~' ~& ]例:7 S% M* m5 u5 X& N% O! w7 J3 u; i
$(findstring a,a b c)
, {( Y' n8 ?% B0 C6 d' q$(findstring a,b c)8 g4 N' x+ {" Z+ K; u
以上脚本,第一个返回“a”字符串,第二个返回空字符串。
. {* G$ k" r( m5 U W( Br 5.dir 函数
5 a4 m2 o+ r1 F& L0 B$(dir )
8 {- w$ e: {* O. N- p% K此函数的功能是从文件名序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部, e$ u$ [, K5 v' B
分。如果没有反斜杠,那么返回“./”。返回文件名序列的目录部分,如下示例:6 q& c# Z/ L- D9 [7 Z: l
$(dir src/foo.c hacks)" a q9 g5 F: [# p
以上脚本运行结果返回“src/”。- G4 t- H* \4 l2 R3 O% [" j
. 6. r notdir 函数
. Q3 {( [4 f" B( H$ k. B% Y$(notdir )
% ~0 l q) |1 F' d/ j此函数的功能是从文件名序列中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后
3 D h. H- A5 u8 l! ^# T" M的部分,返回文件名序列的非目录部分,如下示例:4 f5 \' B& S+ ]# c6 U7 {2 T8 c1 ~# x
$(notdir src/foo.c): i5 e! B- M" j
以上脚本返回字符串“foo.c”# _; P5 K* ~! r W
. 7. h foreach 函数2 ^8 o. q7 q9 _8 ~. Z/ I7 O- G+ I
$(foreach ,,)1 k) z8 [) i$ @
此函数的功能是把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含5 M# }6 Y; k4 N, f
的表达式。每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,! Y/ R8 c7 h: {1 [+ F
最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函% s B& N M9 ^) i x. j
数的返回值。所以,最好是一个变量名,可以是一个表达式,而中一般会使用这
; e) u* R! W% O) b0 C个参数来依次枚举中的单词。如下示例:
5 [8 b0 @9 C @, ]* F+ \2 unames := a b c d
* U4 G! |( y- Q( [files := $(foreach n,$(names),$(n).o)
& K2 n' x3 y3 x以上脚本实现$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出9 K' _, w6 T L
一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以$(files)的值是“a.o b.o c.o d.o”。( n: e. @( `( N) v) W: ] n
(注意,foreach 中的参数是一个临时的局部变量,foreach 函数执行完后,参数的变量将不' C, K6 Y2 u7 B7 |
在作用,其作用域只在 foreach 函数当中)。8 ]2 A$ i8 |6 D8 o; ]3 k$ t
更多内容关注迅为电子! g# C' i! k( ^3 K- u, a- u
|
|