|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
我们在 Windows 下使用 C 语言开发的时候,一般都会有支持 Windows 的开发工具,比如我们学习 51 单 y+ G" k+ a/ c8 I
片机或者 STM32,所使用的的 Keil 开发软件。此类开发工具一般都会集编辑、编译于一体,我们只需要编辑
/ w0 ? X: y6 H0 N+ V* E好代码,电机开发工具的编译按钮,就可以自动为我们编译出可执行的二进制文件了。Ubuntu 下的 C 语言
1 {( U) G. H; x, s2 W开发与 Windows 下的 C 语言开发是不一样的,并没有图形界面的开发工具,而且编辑和编译是分开的。我
% v; V) s9 B8 n* K# R们需要使用文本编辑软件先编写代码,然后使用编译工具(GCC)来编译,最终生成可执行的二进制文件。0 w8 E0 t& a( r7 ]1 R( a) h
如果我们的工程中有多个源文件,在编译的时候我们通常会通过 Makefile 文件来管理整个工程的源文件。
6 o' C2 i9 b1 k8 f% z本章我们来学习如何在 Ubuntu 下进行 C 语言成的编辑,GCC 编译,Makefile 文件的使用。通过本章学习我: l: }. X R7 ]- D$ W1 `) G" {
们可以掌握 Linux 下的 C 语言开发基本方法,为后面的学习做好准备。. Z& w9 v9 w! ?' M1 A) U
3.1 x Linux 下编写第一个 C C 程序 程序7 h2 R3 J- O2 S2 P+ k3 X: x
本章节开始的部分我们介绍了 Ubuntu 下 C 程序开发分成两部分:编辑和编译。Ubuntu 下有很多种文本; g0 D: X" E. V/ q. Y
编辑的工具,如 vim、Gedit、Emacs,这里我们使用前面章节介绍的 vim 文本编辑工具来编辑 C 程序。相信/ F4 z( y' P. |& ^* I9 d
大家在学习 C 语言的时候都是从“Hello World”这个程序开始的吧,下面我们通过这个程序,来学习下 Linux
5 [, D4 D- |0 B% A3 i5 |* g7 a: G7 s: B下 C 程序开发的流程。( Q. w4 s' H" J
3.1.1 代码编写
3 I G0 @& V. q首先我们在用户根目录下建立文件夹“work”,用于保存所有 C 语言的程序,运行结果如下图所示:
' b4 S# [' N- |* @
" ]2 p: W* N3 V1 B q1 M
然后进入创建的文件夹 work,为了方便管理,我们每个例程都创建单独的文件夹,首先我们创建文件# y: i) }6 g w$ O- p* j
夹“hello_world”来保存我们的第一个 C 程序,如下图所示:
5 y$ J0 R) ]5 f h
7 x' L1 f: S6 V" l! i5 {然后进入上图中的 hello_world 文件夹,使用 vi 命令新建文件“main.c”,然后在里面输入下面的代4 Z' q2 R# K" {$ n5 |
码:3 c8 p8 b/ U3 y! b& y
#include ; ?0 ~ W& z9 m1 I( g* h" F
int mian(int argc, char *argv[])
7 }) ]8 c9 B5 {% z+ x3 n{
0 ?) V) T3 ]- f3 E8 |printf("Hello World!\n");
* t L; E; d" z% r+ a, Wreturn 0;
0 q' Q+ i# C) U- d1 q: [# d% p0 z}
" A, R. J( |( |/ l编写完以后保存并退出 vim 编辑器,然后可以使用 cat 命令查看代码是否保存成功,如下图所示:
( ^( {- B& c' {
2 S7 O/ q' H% M0 f$ t" j通过上图可以看到代码已经编辑完成了。$ \ F/ M, d O4 N1 g% l
3.1.2 代码编译
+ R& ^# b0 ^6 ?& P3 r, X# aUbuntu 下使用 gcc 编译器来编译 C 程序,我们在安装 Ubuntu 系统的时候,gcc 编译器 morning 安装好$ B( Z6 Z* V* X, U3 `9 a1 m5 j# s" A
了,我们可以在终端输入“gcc -v”来查看下 gcc 的版本,如下图所示:$ \; K% O9 o1 p( e: o+ ?' Z
* G1 M3 {3 d2 v/ ~6 w" W6 }* o
通过上图可以看到 gcc 的版本信息,说明我们的 Ubuntu 上已经安装了 gcc 编译器了,下面我们来看看
# `7 b2 m+ Z% L# r6 w怎么通过 gcc 编译我们的第一个 C 程序,我们在终端输入“gcc main.c -o main”,然后回车,会生成 main 文
5 n) \' J' o# ^3 J0 o9 V件,如下图所示:% Y. p$ f* c- o3 D
8 C3 [5 u2 g5 q! x5 l/ \
在上面的“gcc main.c -o main”这个命令里面“-o“用来指定编译生成的文件名字,我们指定的是 main,( D) @% q& Z& D
所以在图 3.1.2.2 中可以看到生成了文件“main”,如果我们想生成其它的文件名字,我们只需要修改“-o”- U: B6 ^( I8 ^- _- K# y$ c
后面的 main(修改成您希望生成的文件名字)。经过前面的步骤已经生成了可执行文件 main,现在我们演
3 R! H( C* S5 ?8 k; F' m: \示下在终端如何运行一个可行性的程序,我们直接在终端当前目录下(可执行文件所在的目录下)输入4 C. P' l; Q" J9 X' {. h( l
“./main”,然后回车,就可以运行可执行文件 main 了,运行结果如下图所示:
; U6 E3 }3 B' O& ~" Q( B
: J" N% P- P1 c! i8 g# y& @0 y
在上图中运行的命令“./main”,其中的“./”代表当前目录下。我们可以看到在终端打印出了 Hello1 w+ P: H s/ n6 j* L. D# T; P3 S; O
World!。至此,Linux 下的 C 语言编辑和编译的一整套流程我们就介绍完了。, `* L) w% S5 }
3.2 c gcc 编译器 编译器' u7 O/ g5 g. D% Y! D3 K0 Y) Q& ^
3.2.1 gcc 命令分析
6 k+ n- H, A3 N( K1 X在 3.1 节我们已经使用 gcc 编译了 Linux 下的第一个 C 程序,gcc 命令的格式如下:0 Q, r. K) U- \' p( r: g
gcc [参数] [文件名]
' h7 ~+ u* ~) k7 e6 |9 H主要参数说明如下:
$ v: K `0 A2 w1 }* Z3 {) a% v8 J0 s-c 编译、汇编到目标代码(.o),不链接成可执行文件 T+ z' t7 Y8 d
-g 生成调试信息# H0 u; L7 S* z* }: _
-o 编译完成后生成的文件名,如果不使用该选项,默认生成 a.out 文件6 @2 S2 k: i3 \! O5 ]
-O 对程序进行优化编译,产生的可执行文件执行效率高* b" T9 |. P+ y5 q K
-w 不生成任何警告
6 R1 q: r: ~( _' A; ]$ \-S 仅编译到汇编语言,不进行汇编和链接+ ?( S8 l$ o) S$ C
3.2.2 编译警告错误处理4 }' Y: ]6 r! b. F5 M5 _
我们是 Windows 下使用 Keil 或者其他开发工具,在编译的时候,如果程序有错误,开发工具会提示出
) X; ~) W! C+ h7 k具体的错误信息,可以很方便的定位到问题点,快速的修改出现的问题,gcc 同样也有类似的功能,下面我
/ e3 `) _7 I+ ~" l( r们来看下 gcc 的错误提示功能,首先我们在 work 目录下建立文件夹“test2”,然后使用 vim 在 test2 文件
6 `2 N" [/ d2 {. Q I& l) r夹创建 main.c 文件夹,在 main.c 文件输入如下代码:# B1 w& ?1 d$ Y2 B7 p/ G. ~. S9 J
#include 3 G: @. P$ w2 b1 A
int main(int argc, char *argv[])+ A/ f ]3 Z- o1 l2 y
{
3 n# y k, M% G, x4 t6 J. B6 vint a;
7 C: u6 G+ ?3 Va = 1
4 O3 R0 A& V4 E' `! x$ v) Aprintf("a=\n", a);* X. q% }& g. c, f2 }, p
return 0;' n% m* W$ _% H, I* W* y, R8 F
}
9 L3 w# [. p$ w; ?- A上面代码有两处错误:
. ~0 v8 ?( ]- R: a第 7 行 最后缺少“;”: b8 G, z' R6 O0 x
第 9 行 printf 语法不对,应该为:printf("a =%d\n", a);$ p6 S. x' r$ |2 }* w' K Z+ j
我们使用 gcc 编译 main.c,可以看到 gcc 会提示错误信息,如下图所示:1 c+ c1 W& o! Y0 R! _" G
: _7 c5 b0 b( D' T
从上图中可以看到在 mian.c 文件的第 9 行 printf 前面缺少“;”我们在第 7 行“a = 1”后面加上“;”,
4 M. C: } y# O0 ^+ W5 A然后继续编译,如下图所示:; R. K/ X9 y3 M
8 H% Z) u9 M9 b. |. H' g
从上图可以看出编译提示语法格式不对,我们把第 9 行修改成“printf("a=%d\n", a);”,然后在继续编
- T( k$ w, {1 w5 K0 g* B' ]% {$ q译,如下图所示:1 C% f% B" V' G8 l
' \3 ^$ m. y1 ?; q
我们可以看到这次 gcc 编译通过,最终生成了文件 main。我们在终端执行 main,运行结果如下图所示:% v! P S* e# V( f& p
5 V" P# S+ n- g, I从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出
- }& T# ~/ g. ?程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。0 i( e; t4 P }1 q# a7 r
从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出
2 i4 S" U" R2 M& k程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。
0 h! n& U0 W( f1 u2 p8 I1 P3.2.3 gcc 编译流程
4 m6 w9 a8 ~4 d: L& c3 O+ w7 r2 Vgcc 的编译流程可以分成四个步骤:
6 r6 M$ q, u# W! `( u: ]1.预处理,生成预编译文件(.文件)6 w C( P' \& n
2.编译,生成汇编代码(.S 文件)! f) J5 B* u* S: l* `1 h5 ]
3.汇编,生成目标文件(.o 文件)
; C8 x; z3 I* Z+ O; A' E4.链接,生成可执行文件* @& q8 o* c/ R0 }( w/ C
3 3.3 初识 Makefile' d- E+ j+ X' L9 K! \: R
3.3.1 什么是 Makefile
' }' c! }( E b9 h! V+ e3 I. L在 3.2 章节我们了解了在 Ubuntu 系统下通过 gcc 编译器来编译 C 程序,在我们演示的历程中只有一个
: f) P( _# \; J. d! C- PC 文件,我们直接在终端输入 gcc 的编译命令,就完成了 C 程序的编译。我们在实际开发过程中,如果我们
0 `; H% n4 m* ?的工程有几十个,或者几百几千个 C 文件,我们通过在终端输入 gcc 命令来编译,这显然是不现实的。为0 n h5 d# N0 J$ k9 ?) b4 Y4 p
了解决这个问题我们可以使用“make”命令,它会解析 Makefile 文件中的指令(应该说是规则)来编译整4 T; t6 G, t/ C4 K8 T0 t
个工程。在 Makefile 文件中描述了整个工程的所有文件的编译顺序,编译规则。
% @) L3 H& ~. Z' R' V) ]作为一个专业的程序员,一定要掌握 Makefile 的,我们可以通过 Makefile 能了解到整个工程的处理过程
. V" A n- l; t3 B* s3 [+ {9 j的。- o4 e5 y; g/ R: [
由于Makefile涉及到很多的知识点,以至于可以单独写本书来讲述,所以本章我们只是讲解下Makefile
1 z& S! T. q y6 H; g的基础入门,如果详细的研究 Makefile,可以给大家推荐《跟我一起写 Makefile》这个电子文档,该文档
3 Z5 c( ^( P5 y* v, @已经放在了:i.MX6UL 终结者光盘资料\09_其它参考资料里面了。& q3 f5 U0 M8 t' c; E0 \
3.3.2 第一个 Makefile8 f" ^& b/ |+ C. S
在本节我们建立这样一个工程,计算两个整形数的和,并将结果在终端显示出来。在这个工程中一共有
* c9 R/ W* H! `main.c、calc.c 两个 C 文件和 calc.h 这个头文件。其中 main.c 是主文件,calc.c 负责接收 main.c 传过
$ i3 v" c6 l- Y- h5 p来的数据,然后进行相加。main.c 文件的内用如下: H! J h8 r0 A" @, J5 b' ]
#include ! P1 y% s& p, t' T' C
include "calc.h"& @+ P) N# G* k; w1 g6 `% `
int main(int argc, char *argv[])
) k2 ^' m3 j. y6 q q& m{7 ?6 D. Z4 S1 U; M A
int a = 3, b = 6, sum;
4 r4 p, P3 G1 a! v Esum = calc(a, b);9 g# l8 D4 K s1 {' _! J
printf("%d + %d = %d\n", a, b, sum);
! p5 w6 h) t- V8 d$ \# b/ v8 V& Q+ ^return 0;; o) e; x9 d* C6 b3 ?
}
6 \1 N$ t$ o% Tcalc.c 文件内容如下:- x: z2 ]6 k" f: S8 H( d1 E
#include
& n0 F2 K9 n- {. D' r- `int calc(int a, ing b)4 [0 a* D9 Y }- D7 w5 C
{
$ Q0 {* S$ f% o' B% X3 |return (a+b);/ o; V( x& L) {% }5 X% i6 N
}% j4 J7 {4 d) L" Z# [2 k6 j: z, n! [
文件 calc.h 内容如下:
0 X/ ^1 S+ W6 h: N2 v#ifndef _CALC_H
6 e+ I% T& d1 }#define _CALC_H9 k1 e! \) p5 B) ]2 v. K
int calc(int a, int b);
! P0 a* v# t0 Y#endif) L, P4 S6 y8 y/ p& k
上面就是我们这个工程的所有源文件,我们在终端使用 gcc 编译这个工程,在终端输入“gcc main.c
5 x8 L7 b3 Z& V* o, t9 Acalc.c -o main”,该命令的意思是用 gcc 对 main.c、calc.c 进行编译,然后输出可执行文件 main,运行
- V# M7 @9 l. w( d2 R4 w/ F/ C' f V结果如下图所示:
* g- E' y* K+ i% \
0 e. o( q7 @8 U7 o( X
通过上图可以看到生成了可执行文件 main,我们在终端运行 main 执行文件,运行结果如下图所示:. A4 c0 I8 k' ]& ^
7 F; l5 D5 o. a( {$ b/ H3 j# z
我们可以看到上图的运行结果和我们设计的结果是一致的。由于我们的这个工程是有三个文件,如果
/ t( t% @& r [- q工程有几百个,几千个的文件,或者如果有一个文件被修改,使用上面的命令将会编译所有的文件,如果
+ X! |. G0 Z9 \5 Q% X我们的工程有上万个文件,编译完一次工程所需要的时间就很可怕。最优的方法就是编译过一次以后,如- _: d7 P$ w6 Q" {& w9 D
果后面在编译,只编译修改的文件,这样就会节约很多时间,因此我们修改下编译方法,命令如下:; l) a1 T) O; R3 M0 |9 p0 ~4 u* ]" K
gcc -c main.c
$ s k' j/ R0 h2 pgcc -c calc.c. d! v8 M, z# t- S! o6 {
gcc main.o calc.o -o main
! W0 `" `$ m) }. e: y- F我们在终端输入上面的命令,结果如下图所示:
5 @0 L3 ~- e) a- V* o; V
0 Q) k0 ?* e* _: U% J
上图的第一条和第二条命令里面使用了参数“-c”是把 main.c 和 calc.c 编译成对应的.o 文件,最后* l; Z( y1 `7 h K! K
一条命令是把编译生成的.o 文件链接成可执行文件 main。假如我们修改了 main.c 这个文件。只需将 main.c0 g" `8 ?( s8 e7 y
这个一个文件重新编译下,然后在把所有的.o 文件重新链接成可执行文件,对应的命令如下:
# A6 S" t) q, ^gcc -c main.c
2 A+ I6 \/ w" `& x- Q hgcc main.o calc.o -o main/ W1 y- O, r; |$ J# B
可是这样还有一个问题,如果需要修改的文件有很多,这样工作量也会不小,所以我们需要一个工具:0 ~0 O; ~7 O5 x1 }
1.如果工程没有编译过,就会把工程中的.c 文件全部编译并连接成可执行文件, ]0 l/ x( E5 p5 i; M
2.如果工程中有某些文件修改了,只编译修改的文件并连接成可执行文件
) W2 p+ c6 L2 W9 _5 h* R% T3.如果工程中的头文件修改了,那么就要编译所有引用这个头文件的.c 文件,并且连 接成可执
! L0 f4 Y6 f' {行文件
+ Q& T& k! Q$ i0 u8 }! D我们开头说的 Makefile 就是完成这个功能的,下面我们在工程中建立一个 Makefile 文件来实现这样的功% u, l: d' u1 s* a
能(注意:文件名字必须为 Makefile,大小写是区分的)。我们使用 vim 创建 Makefile 文件(Makefile
3 D+ ^* B5 i) T6 I和我们的 main.c、calc.c 在同一级目录下),然后输入下面的脚本:; \) V9 B+ G9 Z Q7 f3 h1 T
main:main.o calc.o, S% G3 ^: L& a4 P& U1 Z
gcc -o main main.o calc.o
7 r. R5 J1 Q3 bmain.o:main.c: Z8 s8 J% R# ^7 @
gcc -c main.c
, Q# E# p+ t V* L$ y1 ^& ycalc.o:calc.c. h3 f; \# p1 X
gcc -c calc.c
( U# U7 Z8 b; `' {7 S/ ^clean:0 ^3 d, Y0 f. y4 c2 K8 a+ v7 k) T& o! j
rm -RF *.o# U; a2 f6 e8 k: K* M& I
rm -rf main
( n3 _& }/ E* x1 p5 ]0 v7 a上面脚本缩进的行需要使用“Tab”键缩进,不要使用空格,这是 Makefile 的语法要求,编写完成的脚本 O/ {7 h' K' T! x
如下图所示:( Z8 _7 D8 S9 ?. P1 M. E
& r# z" z% x1 J
编写好 Makefile,保存并退出,然后我们在终端输入“make”命令来编译我们的工程,make 命令会在
! P6 ^- Y$ r# I( X当前目录下查找“Makefile”文件,如果存在的话就按照 Makefile 里面的规则进行编译,如下图所示:4 L* g" k# H6 N( p0 B
1 j7 C. h/ i% V+ `+ L& J$ h( s, G通过上图可以看到编译产生了 main.o、calc.o 和 main 执行文件,说明编译成功了。接下来我们修改) p3 K2 A" q8 p0 \/ ^
下 main.c 这个文件,如下图所示:
- A# [( l, b) q8 Z h q; `
3 A" z: u1 B0 e3 e8 n然后保存并退出,然后在终端输入“make”再次编译下工程,如下图所示:
* O; a, N; @2 o o9 @
- U% P7 G. N5 h5 a5 `
通过上图我们可以看到只重新编译了修改的 main.c,并最终重新链接生成可执行文件 main,我们在终$ C' q8 S" G3 ?- J# N% Y7 o. C3 @
端运行可执行文件 main,如下图所示:* q3 t5 l, }6 S7 p. D; a) R2 w1 h
; Q0 G5 D+ {* T( t8 P* s' W" p' z+ N
从上图的运行结果可以看到最后的结果等于 10 了,和我们程序的设计结果是一样的。2 }! [) w: M* |% B6 a% @, Q7 U' k
3.4 e Makefile 语法 语法
6 y* r2 r. v k3.4.1 初识 Makefile
2 F/ m2 }' ?. S. F( _# v. Z4 eMakefile 文件是由一些列的规则组合而成的,格式如下:, E4 W5 k% b2 S7 _( p1 m% W
target(目标文件) ...: prerequisites(依赖的文件) ...6 f( J- y( ?; G+ k9 o( q
command(命令)
' {4 H5 I* C* m/ j& a8 o...2 P Z1 p7 @* f' T6 m! k( P$ n
...9 X/ D& `2 U! E
比如 3.3.2 中写的 Makefile 的规则:( b7 o# O- _0 P8 b. i$ U
main.o:main.c
+ C9 I3 z& F7 M( I, c, kgcc -c main.c
1 `. w2 h, X3 z5 b7 j这条规则的 main.o 是目标文件(将要生成的文件),main.c 是依赖的文件(生成 main.o 需要的文件),0 j' l( d! L: w4 X) \! x- g
“gcc -c main.c”是生成 main.o 需要运行的命令。e Makefile 中每行的脚本如果有缩进的情况,必须使用$ _5 A- C+ G/ a$ x
“ Tab ” 键缩进,切记不能使用空格缩进(这是 e Makefile 的语法要求),大家一定要切记!
/ P2 b' l# i5 Y$ x) \7 b4 }下面我们来分析一下图 3.3.2 章节中写的 Makefile 文件,脚本如下:) j6 A" \& Y$ H8 e0 D
1 1 main:main.o calc.o* p/ I" V$ f* H# X& I \
2 2 gcc -o main main.o calc.o
# ?/ p$ ]6 ^: }& n9 n# z" E0 N3 3 main.o:main.c
6 w1 `: j" N/ Q/ m% ?+ _" O4 4 gcc -c main.c- R1 V( _' N3 T
5 5 calc.o:calc.c5 e& j! u( [4 \! G3 e
6 6 gcc -c calc.c) x3 F# k6 X4 l$ Y
7 7
, Q( F8 w' C! b. E8 8 clean: Z& ]& ~% i9 U4 }8 N8 j E2 }
9 9 rm -rf *.o% L/ [# F: O. L$ d
10 rm -rf main. J6 H2 o0 O, b; W: ]
该脚本一共有 4 条规则,1、2 行是第一条规则,3、4 行是第二条规则,5、6 是第三条规则 8、9、109 |/ _6 B; ~4 d. x
是第四条规则。我们在运行 make 命令的时候,会解析当前目录下的这个 Makefile 文件里面的规则,首先
- r. v" a9 h" x1 k7 s9 q2 i5 X解析第一条规则,第一条规则中的目标文件是 main,只要完成了该目标文件的更新,整个 Makefile 的功能
* Z& ~/ n8 j% m; ~5 K( ~: B( U* D$ B9 @就完成了。在第一次编译的时候,由于目标文件 main 不存在,则会解析第一条规则,第一条规则依赖文件
2 m, X* ^2 |- umain.o、calc.o,make 命令会检查当前目录下是否有这两个.o 文件,经过检查发现没有,然后 make 会在& l1 q: g& I9 j3 C+ e% Z
Makefile 中查找分别以 main.o、calc.o 为目标的规则(第二条,第三条规则)。执行第二条规则依赖的文 I* V5 M" u$ R& [7 r2 s
件是 main.c,make 命令检查发现当前目录下有这个文件,然后执行第二条规则的命令“gcc -c main.c”
3 q, E- M. Q1 y, e6 g% t* n+ L生成 main.o 文件。然后执行第三条规则,第三条规则的目标文件是 calc.o,依赖的文件是 calc.c,make" I; {$ ?' W! O, N& L
命令检查发现当前目录下存在该文件,然后执行第三条规则的命令“gcc -c calc.c”生成 calc.o 文件,
$ c) `7 j, L* |. F+ e3 A至此第一条规则依赖的 main.o、calc.o;两个文件已经生成了,然后运行第一条规则的命令“gcc -o main+ ?4 Q0 \( d1 h8 I' q K
main.o calc.o”生成 main 文件。因为 make 命令运行的时候会从 Makefile 的第一条规则开始解析,然后5 N* J. X( O- y" B
根据第一条规则的依赖文件去遍历文件中的“对应规则”,然后在根据“对应规则”的依赖文件去遍历“对* \# Q' u% W9 S9 h% }2 ?
应的规则”,采用这样递归的方式会遍历出完成第一条规则所需要的所有规则。下面我们来看看第四条规1 z, i, f0 l9 S4 r
则的目标文件是 clean,我们通过查看发现该规则与第一条规则没有关联,所以我们在运行 make 命令的时* u5 h+ N: f: o" r: V
候,不会遍历到该规则。我们可以在终端输入“make clean”命令来运行第四条规则,第四条规则没有依" z( f$ F" t1 z, d9 q8 N7 h
赖的文件,所以执行运行命令“rm -rf *.o”和“rm -rf main”,这两条命令的功能是删除以.o 问结尾的
Q8 X4 R( n0 J3 Y所有文件,删除文件 main,运行如下图所示:$ f" `7 ]- p0 g" A: X* ]
& f/ h+ B9 H4 l
通过上图可以看到 main.o、mcalc.o 和 main 三个文件已经删除了。通过该规则我们可以清除编译产生7 I" ~4 x+ f! J7 p2 w9 M1 q$ m4 j
的文件,实现工程的清理。
- k$ A$ y" z' y+ z, W我们再来总结一下 make 命令的执行过程:
& D$ ]4 { H# D3 i; F1.make 命令会在当前目录下查找以 Makefile 命名的文件! [. q, O w! j
2.找到 Makefile 文件,就会按照 Makefile 里面的规则去编译,并生成最终文件
. O7 Y0 Q$ X) `# ^ I3.当发现目标文件不存在或者所依赖的文件比目标文件新(修改时间),就会执行规则对应的命令来更新。
" e: {/ v" L# z9 W3 ?我们可以看到 make 是一个工具,他会通过 Makefile 文件里面的内容来执行具体的编译过程。
6 @7 F( j* L) Y5 E) J1 a7 x* n3.4.2 Makefile 的变量5 h, S5 \. Y3 U
在 3.3.2 章节中的 Makefile 第一条规则:
, q0 z+ k* {; j, s0 r; o/ b( ymain:main.o calc.o9 z. \8 j: ]! a6 @! b& x0 Y% v
gcc -o main main.o calc.o; ~( f+ F- `8 ^7 ^3 j: U4 H( U
在该规则中 main.o、calc.o 这两个文件我们输入了两次,由于我们的额 Makefile 文件内容比较少,
5 |1 N; z# x7 V6 I' P& E" U如果 Makefile 复杂的情况下,这种重复的输入就会非常占用时间,而且修改起来也会很麻烦,为了解决这
3 [, C7 U" `3 |. \+ N4 O/ |个问题,Makefile 可以使用变量。Makefile 的变量是一个字符串。比如上面的规则我们声明一个变量,叫6 a! }$ P- y0 S) B0 e! p: W9 ~% T
objects,objs 或者是 OBJ,反正不管是什么,只要能够表示 main.o、calc.o 就行了,我们修改上面的规
2 H- @7 {" k, {& q& p- r则3 J* x! d' f/ I& e; x
1 1 objects = main.o calc.o) _; i; y' n+ ?, _$ e& T% F% g! m
2 2 main ( objects)* K8 {0 C$ s9 v) i5 m5 @
3 3 gcc -o main $( objects)& g- E# e: u2 r1 [
我们来分析下修改后的规则,首先第一行是我们定义了一个变量 objects,并给赋值“main.o calc.o”,
! T& Y: c& s; h u9 C6 M, i第二行、第三行用到了变量 objects。Makefile 中的变量引用方式是“$(变量名)”,变量 objects 的赋值/ y8 S% F& C4 v7 H8 q: N! s
使用“=”,Makefile 中变量的赋值还可以使用“:=”、“?=”、“+=”,这四种赋值的区别如下:
7 d! d k" W% M8 ^: {: e1. “= = ” 赋值符5 e$ M' T3 D+ s; E/ j! S% r
我们先在用户根目录的 work 目录下创建一个 Makefile 脚本,输入下面的内容:9 k, a) ~" X2 K
1 ceshi1 = test
1 I0 P8 u5 S s; S) s' H2 ceshi2 = $(ceshi1)
* q( b' k. O' @3 } R3 k+ q$ o3 ceshi1 = temp
" S/ {( u2 M6 J, V4
/ T6 } q1 {1 }0 w9 _ h5 out:! U4 Z9 y; T" ~" N6 |' y, G9 m
6 @Echo ceshi2 (ceshi2)9 Z' p+ ~0 s4 N& y
第一行我们定义了变量并赋值“test”,第二行定义了变量 ceshi2 并赋值变量 ceshi1,第三行修改变量& t: _ j. S6 P* v
ceshi1 的值为“temp”,第五行、第六行是输出变量 ceshi2 的值。我们在终端输入“make out”命令,如4 }. h* J9 c* x
下图所示:
( T. P1 w, a ]9 k( x# A. A
1 I6 u: D" R2 ^" g3 j+ x% x+ H在上图可以看到变量 ceshi2 的值是 temp,也就是变量 ceshi1 最后一次的赋值。
, O7 e! m% ]0 t% o* s& e d2. “ := ” 赋值符$ |+ x9 s8 G8 Q5 C' g9 j
我们修改“=”赋值符中的代码,第二行的“=”改成“:=”,代码如下:
2 t+ E; ]1 m( n8 Y2 L7 [; i1 ceshi1 = test
4 a5 Y& {6 J$ G5 s( [2 ceshi2 := $(ceshi1)
% ~9 ~. {# z( z- I n1 C( o3 ceshi1 = temp
& Z) h* L4 w, g% F, j4' a, R1 t# }' W2 d
5 out:, X! v2 J3 E' g" i3 g% h
6 @echo ceshi2 (ceshi2)+ g+ g7 U& U3 y# A( v
我们在终端输入“make out”命令,如下图所示:! n6 B& k5 c4 M# B& E. }4 h
, Z# M; S! S% s
我们可以看到上图的运行结果输出变量 ceshi2 的值是 test,虽然在第三行我们修改了变量 ceshi1 的
: G- w6 Z9 ~8 O- w: F值,通过本实验我们可以看到“:=”赋值符的功能了。' \/ K: M5 G8 E
3. “ ?= ” 赋值符
. y( A7 }+ R- Z0 U' Oceshi ?= test6 K( E Y( @! L1 n: Y- A3 j! l# {
“?=”赋值符的作用是如果前面没有给变量 ceshi 赋值,那么变量就赋值“test”,如果前面已经赋值了,
# b+ f1 I4 A' [: ~1 D2 ]就使用前面的赋值。
2 I% p9 Y$ W- r, o4. “ += ” 赋值符
# a% F4 Q7 }# h) o- Xobjs = main.o
( p0 X! d2 j) E( u7 ^# aobjs += calc.o
" h, ^1 R4 k" {1 `( F" S% D0 C上面的脚本最后变量 objs 的值是“main.o calc.o”,“+=”赋值符的功能是实现变量的追加。: L" q' m1 B& Y1 g5 v
3.4.3 条件判断$ n3 E# {4 X( K# L4 y
使用条件判断,可以让 make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量 k' o5 k9 [+ s# s- {2 ]
的值,或是比较变量和常量的值。其语法有下面两种:6 |! i- Y- _, q8 Z1 L. y y; K: Q
1.. b" S1 {' z# i' t
<条件比较>
y2 v/ {6 m% Y+ U2 k[条件为真时执行的脚本]: \" I4 K: j2 i# m7 A3 x
endif0 v+ O2 K/ Q! a- o% ^- B7 G
2.
; y w' z% I& }4 |. P( @' Q<条件比较>& _' E- ~' D0 H) O$ S
[条件为真时执行的脚本]
% P: h- M& ]( l0 X( Velse
$ I* j. E5 s. M9 q0 g/ V: \: ~[条件为假时执行的脚本]2 O+ e) C) \7 a0 ?3 \
endif
2 ^/ W. R8 }$ Q# i) y条件比较用到的比较关键字有:ifeq、ifneq、ifdef、ifndef。4 K) \+ h7 A2 G% O
ifeq 表示如果比较相等,语法如下:
/ A' }( G& v" H3 t1 K" W. M5 Cifeq(<参数 1>, <参数 2>)! u, [+ h% L1 S2 ]
ifneq 表示如果不相等,语法如下:6 S3 l! ~8 ~9 z2 S) `
ifneq(<参数 1>, <参数 2>)
* a4 c) `8 h* p, M4 S8 Fifdef 表示如果定义了变量,语法如下:
/ }* X1 f0 U- M8 X* }7 uifdef <变量名>6 U! e: W0 J( }1 l/ |) Y' {
ifndef 表示如果没有定义变量,语法如下:
3 |3 N1 f# u$ _. B: Q Q* Eifndef <变量名>6 h% ~+ \# B3 Q
3.4.4 使用函数
, [0 R: r2 A- s/ s在 Makefile 中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make 所. ~! R' x% @6 j4 L$ \, V+ J& q
支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。. N" G7 n5 s' m* A/ O# r
函数的调用很像变量的使用,也是以“$”来标识的,语法如下:
( \: j* W. m- L, k. a3 O: U3 [" J. m$(<函数名> <参数集合>)8 Y5 x( t1 w% j2 C, y7 U1 ?: v
或者:& B0 f9 E P8 X+ R: h. N
${<函数名> <参数集合>}
, m. X) x* Y: w. t1 Z函数名和参数集合之间以空格分隔,参数集合的参数通过逗号分隔。函数调用以“$”开头,以圆括号或花
1 E" f- B, z* Y0 c: |括号把函数名和参数括起。感觉很像一个变量。函数中的参数可以使用变量。为了风格的统一,函数和变
" H. U$ E+ d/ S量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。
! a1 w% G2 K0 F5 e6 }1 W因为统一会更清楚,也会减少一些不必要的麻烦。- [. v+ G$ {1 |8 [* H4 ?/ N
接下来我们介绍几个常用的函数,其它的函数可以参考文档《跟我一起写 Makefile》。* T! L, g) b8 B! f2 Q9 ^; U+ k
t 1.subst 函数
! W- Q/ s" I. z: I" T$(subst ,,): w, p& }$ h% \# _4 Z' | s+ c7 Y! O
此函数的功能是把字串中的字符串替换成,函数返回被替换过后的字符串。如下示例:
. F( h% r% v% B. x: J- |$(subst ee,EE,feet on the street)
0 Z. P& I( N; o& f" T以上脚本实现把字符串“feet on the street”中的“ee”字符串替换成“EE”字符串,替换后的字符串. P# v5 h- Z: W4 N3 {3 Q
为“feet on the strEEt”。
, I) D$ k5 f& Z% }2 J1 ]- q. 2. t patsubst 函数( h" o( g5 M4 H$ K* u) e
$(patsubst ,,): a( G+ M+ h& _, q& k
此函数的功能是查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式
' M; q Z$ i: Y8 h2 {3 h! x& G,如果匹配的话,则以替换。这里可以包括通配符“%”,表示任意长
- ~- L7 U% |5 g* J$ S4 B度的字串。如果中也包含“%”,那么中的这个“%”将是中的那个
+ `. ^4 r, K; Z“%”所代表的字串。(可以用“\”来转义,以“\%” 来表示真实含义的“%”字符)。函数返回被替换2 \# j! @& K Z3 E* k8 i+ C
过后的字符串。如下示例:
0 H: m+ I/ }( p' K$(patsubst %.c,%.o,x.c bar.c)
% {% Q) L- E$ k% d以上脚本实现把字串“x.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.o bar.o”
4 O4 T* m* Q9 op 3.strip 函数
/ {( h& _$ g' u3 d$ x$(strip )
5 C# A9 u4 G' x; h8 w: y8 h3 h此函数的功能是去掉字串中开头和结尾的空字符,函数返回被去掉空格的字符串值。如下示例:
. Q, ~4 u( ^; J$ I3 c$ L5 y+ Q9 j1 o$(strip a b c )) y, N! C9 S6 R8 F: ?4 O+ z6 P
以上脚本实现把字串“a b c ”去掉开头和结尾的空格,结果是“a b c”。
$ ?: p7 J/ T" j" Z6 p. 4. g findstring 函数
7 f- c) U5 R( F5 \$(findstring ,)
& h1 ]7 m. [4 g( t此函数的功能是在字串中查找字串,如果找到,那么返回,否则返回空字符串,如下示
' E- @" Z) i N$ [9 ~2 M例:
/ p, Z, R- Q# C( R& u5 E$(findstring a,a b c)& w% Y) b5 T! D/ d P
$(findstring a,b c). S. l) s! X$ v8 |3 c8 o
以上脚本,第一个返回“a”字符串,第二个返回空字符串。
# V% K9 X5 Q. D+ Ar 5.dir 函数
G0 k! ]6 `; g# t$(dir )7 h$ K/ A( U* t
此函数的功能是从文件名序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部, g0 R- X2 J& s" ~5 L+ v J5 t$ j- p
分。如果没有反斜杠,那么返回“./”。返回文件名序列的目录部分,如下示例:
& v- d6 `! q1 u1 d" R$(dir src/foo.c hacks)3 @' n* f0 ~7 H$ g" t, Z
以上脚本运行结果返回“src/”。
5 l: Y' m3 F( |$ l. 6. r notdir 函数
8 H% D( T# `. ?) B3 i$(notdir )! O7 J/ N" K% ^
此函数的功能是从文件名序列中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后7 h! y3 p, E: L4 P
的部分,返回文件名序列的非目录部分,如下示例:8 V7 e6 a, C; E) q
$(notdir src/foo.c). P# [8 }( D1 w! o4 I$ h
以上脚本返回字符串“foo.c”
# {, E z$ e8 w6 a/ _- D6 s& a. 7. h foreach 函数9 a* S8 E6 n9 a% D! }: |1 w G9 [
$(foreach ,,)
" c" Q/ p6 R5 X- u( ]0 g, b6 g此函数的功能是把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含2 |& q$ e% V7 F: `6 W. z( r
的表达式。每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,& M) n9 b6 n+ j% S0 E' b
最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函
E/ O8 {8 }3 B+ U5 q9 @- k数的返回值。所以,最好是一个变量名,可以是一个表达式,而中一般会使用这
' o3 l) ]! m* B个参数来依次枚举中的单词。如下示例:, p; w8 D% ?" K& |4 w9 I3 _. ?" D
names := a b c d
2 j( d. w$ u O4 \9 o2 H$ _files := $(foreach n,$(names),$(n).o)
4 x3 m9 C9 A: o9 N* C, [ u" C以上脚本实现$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出$ P* P8 L' ~7 L N* ?- i' n7 Z
一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以$(files)的值是“a.o b.o c.o d.o”。
+ f' O* A$ e4 x& R: V* i(注意,foreach 中的参数是一个临时的局部变量,foreach 函数执行完后,参数的变量将不0 B$ O1 t' C, {* s3 e8 Z3 |( x
在作用,其作用域只在 foreach 函数当中)。
- T2 |+ N8 Z1 l/ a4 B- X: H更多内容关注迅为电子/ `7 H% X/ j9 C' e
|
|