找回密码
 注册
关于网站域名变更的通知
查看: 317|回复: 1
打印 上一主题 下一主题

迅为IMX6ULL终结者开发板Ubuntu下C编程入门

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-5-11 13:56 | 只看该作者 |只看大图 回帖奖励 |正序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
我们在 Windows 下使用 C 语言开发的时候,一般都会有支持 Windows 的开发工具,比如我们学习 51 单
3 [. h& y9 |3 u- j片机或者 STM32,所使用的的 Keil 开发软件。此类开发工具一般都会集编辑、编译于一体,我们只需要编辑
: q, E: p$ D2 P好代码,电机开发工具的编译按钮,就可以自动为我们编译出可执行的二进制文件了。Ubuntu 下的 C 语言( ?- Z2 {: @4 j; E8 v
开发与 Windows 下的 C 语言开发是不一样的,并没有图形界面的开发工具,而且编辑和编译是分开的。我3 E1 e& h" ?. x" i' y; ]
们需要使用文本编辑软件先编写代码,然后使用编译工具(GCC)来编译,最终生成可执行的二进制文件。
7 b: Z; d( \0 s5 B& `如果我们的工程中有多个源文件,在编译的时候我们通常会通过 Makefile 文件来管理整个工程的源文件。$ }- W6 C4 z  S7 d) |  J
本章我们来学习如何在 Ubuntu 下进行 C 语言成的编辑,GCC 编译,Makefile 文件的使用。通过本章学习我
/ E  S, ?: o! T, Q; t们可以掌握 Linux 下的 C 语言开发基本方法,为后面的学习做好准备。7 o" Z+ b$ L8 c" L2 o4 s+ k8 }: G0 ]
3.1 x Linux  下编写第一个 C C 程序 程序
% B) @, F3 K! K" i本章节开始的部分我们介绍了 Ubuntu 下 C 程序开发分成两部分:编辑和编译。Ubuntu 下有很多种文本
0 s0 X# ?4 Q) C- z# R6 q编辑的工具,如 vim、Gedit、Emacs,这里我们使用前面章节介绍的 vim 文本编辑工具来编辑 C 程序。相信
; }% q  i$ t5 x& ?& l" C大家在学习 C 语言的时候都是从“Hello World”这个程序开始的吧,下面我们通过这个程序,来学习下 Linux
' E. U1 {. j; t  |! Q8 p下 C 程序开发的流程。
2 V9 h: @6 t8 j  D7 ~3.1.1  代码编写
/ V- i- Q: b" Z$ y' q5 z首先我们在用户根目录下建立文件夹“work”,用于保存所有 C 语言的程序,运行结果如下图所示:
8 w7 c  q; ~2 c7 ^
7 W% ^5 t/ v8 A6 \7 D然后进入创建的文件夹 work,为了方便管理,我们每个例程都创建单独的文件夹,首先我们创建文件( v  }& N' y% g( }
夹“hello_world”来保存我们的第一个 C 程序,如下图所示:: b( S$ g+ T/ A/ O7 }; L9 e/ \
" R2 R: t# @& i7 L- l- a7 L- P
然后进入上图中的 hello_world 文件夹,使用 vi 命令新建文件“main.c”,然后在里面输入下面的代8 v# D- l- j, g7 s5 I2 W( K
码:
  v% X' x4 I8 v) f$ n#include 9 z( p1 i( K6 f! f. [. k
int mian(int argc, char *argv[])# x4 W. T) }. e2 d; k, u% F" H
{
# t* l% Q8 M; u% G/ Xprintf("Hello World!\n");
) I. E* m& Z  Treturn 0;
- s1 D1 q9 K; r. b2 J) [& ^}( ~" l1 P  N5 K' O/ k9 n( @, s0 d
编写完以后保存并退出 vim 编辑器,然后可以使用 cat 命令查看代码是否保存成功,如下图所示:7 Y4 d  A8 P$ x: y& E1 Q
7 }1 j. q- O: x" o$ G  R5 k
通过上图可以看到代码已经编辑完成了。/ ^; A9 f( N4 {) x2 m& _' O* s
3.1.2  代码编译
: z9 Y, V* U6 d2 D$ T2 j! k8 zUbuntu 下使用 gcc 编译器来编译 C 程序,我们在安装 Ubuntu 系统的时候,gcc 编译器 morning 安装好
* H0 ~% Y) a4 {5 k& G4 ]了,我们可以在终端输入“gcc -v”来查看下 gcc 的版本,如下图所示:
; f7 s& P' s4 x' N' o0 [% k$ s6 I5 W  Q: j 5 j! H" D$ A( X6 g4 K5 L! c
通过上图可以看到 gcc 的版本信息,说明我们的 Ubuntu 上已经安装了 gcc 编译器了,下面我们来看看
, {* f. }  c8 X5 n% v怎么通过 gcc 编译我们的第一个 C 程序,我们在终端输入“gcc main.c -o main”,然后回车,会生成 main 文
  e% d8 k2 `5 i0 j1 t件,如下图所示:
8 h& Y, |$ ?8 S2 ]+ h4 q
7 P) s2 l2 X* L2 g" p$ k在上面的“gcc main.c -o main”这个命令里面“-o“用来指定编译生成的文件名字,我们指定的是 main,
6 F5 Y( b* w4 s: ]! J% [& B所以在图 3.1.2.2 中可以看到生成了文件“main”,如果我们想生成其它的文件名字,我们只需要修改“-o”1 B1 R# a: n, Z
后面的 main(修改成您希望生成的文件名字)。经过前面的步骤已经生成了可执行文件 main,现在我们演1 e4 j  N) I( C9 C3 D$ E& C
示下在终端如何运行一个可行性的程序,我们直接在终端当前目录下(可执行文件所在的目录下)输入$ W; M+ T7 R1 Q' r; E, K
“./main”,然后回车,就可以运行可执行文件 main 了,运行结果如下图所示:
# ~: e4 d* s' b4 d* G* U 2 P! L2 ~8 e* B$ A& @3 I6 K  y
在上图中运行的命令“./main”,其中的“./”代表当前目录下。我们可以看到在终端打印出了 Hello
( Y0 Z  I! O) g4 V% P  ?4 rWorld!。至此,Linux 下的 C 语言编辑和编译的一整套流程我们就介绍完了。
2 F( m6 u' q) h) d1 p2 e+ f3.2 c gcc 编译器 编译器
* V$ {' A4 [. i  k3.2.1 gcc  命令分析
# S# f2 a/ S; H1 Y' ?% q- a8 w在 3.1 节我们已经使用 gcc 编译了 Linux 下的第一个 C 程序,gcc 命令的格式如下:
& ~# S! e: Y, ]3 x9 d+ T) m# I1 fgcc [参数] [文件名]
0 J8 g9 Q& |( Z主要参数说明如下:
" b: u% \, S' k" @2 G8 d-c 编译、汇编到目标代码(.o),不链接成可执行文件8 s0 G$ b& {! r' z) }: Q) B6 T
-g 生成调试信息4 g* u+ o7 S8 ^  ?. Y" M# I2 P% H* O
-o 编译完成后生成的文件名,如果不使用该选项,默认生成 a.out 文件
) t( F0 P- A; c# W-O 对程序进行优化编译,产生的可执行文件执行效率高8 |* b0 w, \: D* O7 l: i) s" ]# Z
-w 不生成任何警告8 F/ a4 x; i% m( |$ E% B0 b3 O
-S 仅编译到汇编语言,不进行汇编和链接4 t& {0 Z4 o: l/ R
3.2.2  编译警告错误处理
3 H) p8 {3 J! s# I* _* {& d6 I' @  n* j我们是 Windows 下使用 Keil 或者其他开发工具,在编译的时候,如果程序有错误,开发工具会提示出
" }# H3 W/ I% d7 n& Q' T) p8 W" h具体的错误信息,可以很方便的定位到问题点,快速的修改出现的问题,gcc 同样也有类似的功能,下面我' T9 A, G! ~/ S, N
们来看下 gcc 的错误提示功能,首先我们在 work 目录下建立文件夹“test2”,然后使用 vim 在 test2 文件5 c4 s* r2 A) i( r) y
夹创建 main.c 文件夹,在 main.c 文件输入如下代码:! U( t9 a: T- X. u
#include
$ c. x7 r1 M5 y* G5 y" k" sint main(int argc, char *argv[])4 Q6 a% l: c& v' h* D9 ]
{5 J9 j7 U. O" |3 V! v
int a;% m' q' M4 v" ~
a = 1
! F. s9 k1 c9 ^7 L% {5 Q& M7 b4 nprintf("a=\n", a);
  n$ r' q0 ^2 V) B0 K# y+ Zreturn 0;
6 S# C3 E: }1 h% J! N9 Y}
3 j/ \% v; h: n, [& i上面代码有两处错误:
1 e4 d6 T, v; y# }- t5 O第 7 行 最后缺少“;”8 V! F4 j# k9 l( W) M# Z$ @
第 9 行 printf 语法不对,应该为:printf("a =%d\n", a);
' x1 G/ H) _3 y, }0 u" I% ~我们使用 gcc 编译 main.c,可以看到 gcc 会提示错误信息,如下图所示:
. T9 m4 w# r( r/ O1 P ) l: M$ |& M- z6 W1 f
从上图中可以看到在 mian.c 文件的第 9 行 printf 前面缺少“;”我们在第 7 行“a = 1”后面加上“;”,7 }. _6 A( [0 G! r5 D# G5 i! e
然后继续编译,如下图所示:
9 ~; s3 X( ~5 g3 b& ^ ' ^$ X' J$ A; `( H
从上图可以看出编译提示语法格式不对,我们把第 9 行修改成“printf("a=%d\n", a);”,然后在继续编
. ^% P/ B5 r3 c1 b9 V+ @, _译,如下图所示:
5 Y6 [7 f: g6 R9 K* s6 ^& ^
: m. O' x2 T4 f4 D0 u7 A我们可以看到这次 gcc 编译通过,最终生成了文件 main。我们在终端执行 main,运行结果如下图所示:
+ m  j/ L/ h0 W) b
- I- {  e. k# r6 J  z& |从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出8 [: K! m0 O. J; s- ^3 o2 }0 Z
程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。
1 @, z4 R2 p9 P1 Z7 U0 G# w从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出
+ m: t3 u. U2 }程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。
8 d2 I4 O) E  [  V3.2.3 gcc  编译流程$ ?: w8 k& S2 c- w6 v) W  V$ T6 M
gcc 的编译流程可以分成四个步骤:$ h: S- ]  R+ o: n, [; r. X* ?
1.预处理,生成预编译文件(.文件)
8 y- p3 a( T2 J5 R2.编译,生成汇编代码(.S 文件)( o6 O( I$ E$ `3 Q
3.汇编,生成目标文件(.o 文件)
& ^8 |' z" X- z/ P) B% q4.链接,生成可执行文件
4 y6 @  N/ a" z1 @: k9 E* G3 3.3  初识  Makefile
$ {0 y: e# U2 I5 o. q# s2 Q6 ^! F$ l3.3.1  什么是 Makefile" d& f$ T# u- H0 T- b! }& }
在 3.2 章节我们了解了在 Ubuntu 系统下通过 gcc 编译器来编译 C 程序,在我们演示的历程中只有一个& R; d8 h- R1 k/ @" G: j
C 文件,我们直接在终端输入 gcc 的编译命令,就完成了 C 程序的编译。我们在实际开发过程中,如果我们
4 S: F2 k' K1 n, c$ a) B4 E/ A9 r的工程有几十个,或者几百几千个 C 文件,我们通过在终端输入 gcc 命令来编译,这显然是不现实的。为
. q/ S8 O; d. W. ?" n0 e了解决这个问题我们可以使用“make”命令,它会解析 Makefile 文件中的指令(应该说是规则)来编译整
! K9 {" ^" }- \& V2 Y& U个工程。在 Makefile 文件中描述了整个工程的所有文件的编译顺序,编译规则。
4 R- K( q3 O! u1 S" q7 i4 p作为一个专业的程序员,一定要掌握 Makefile 的,我们可以通过 Makefile 能了解到整个工程的处理过程
  i$ f4 S1 r; C% X$ D" a- X的。- l! ?8 y( \, F
由于Makefile涉及到很多的知识点,以至于可以单独写本书来讲述,所以本章我们只是讲解下Makefile2 q) m0 c+ G  w% a1 c4 D2 R
的基础入门,如果详细的研究 Makefile,可以给大家推荐《跟我一起写 Makefile》这个电子文档,该文档
9 o8 z: a7 \! C- U( v( `. j已经放在了:i.MX6UL 终结者光盘资料\09_其它参考资料里面了。8 c; G7 b2 ~$ D) C3 h8 i' c
3.3.2  第一个 Makefile* z4 @; L# e# z. \5 |9 x
在本节我们建立这样一个工程,计算两个整形数的和,并将结果在终端显示出来。在这个工程中一共有
9 K3 c& Y+ Y8 G1 q$ q8 x0 Z3 r2 k1 rmain.c、calc.c 两个 C 文件和 calc.h 这个头文件。其中 main.c 是主文件,calc.c 负责接收 main.c 传过& W# W  R& x$ V& x7 L; w% o# X
来的数据,然后进行相加。main.c 文件的内用如下:* p, v2 Y/ f. b, [
#include 0 c. g- v. T( z! L
include "calc.h"4 w$ H1 e3 Y. m8 E
int main(int argc, char *argv[])* h6 H) P: [0 j( [$ z" S
{
8 P. P- |9 z2 [5 u# J# Zint a = 3, b = 6, sum;* q/ p# s. K- }& V+ l
sum = calc(a, b);; D: O' k2 y6 W/ o! l+ U
printf("%d + %d = %d\n", a, b, sum);6 ]# u; l4 X; R/ X3 b' @7 E3 R
return 0;
" R- [: t/ r+ F( I" m4 O0 j. [& a5 z, {! u}8 A- L, x# ~" x  ~2 g7 v
calc.c 文件内容如下:. }5 J" P2 [3 p% T
#include
/ T8 i5 ^: }* a+ |2 {+ mint calc(int a, ing b)$ q" S$ D0 c) v: V5 c
{) R) P! i& D+ E" M/ }
return (a+b);- r! n, t% V0 u( ?8 O1 |
}
7 c% n; A/ k( d! [文件 calc.h 内容如下:
8 J0 k" i5 C# i+ R( F: [8 s#ifndef _CALC_H5 z9 ^9 k  H, [# N
#define _CALC_H, n! @4 M) C" L
int calc(int a, int b);6 l8 X( G, o& \4 S$ `. t' N
#endif
6 ^9 `9 K! V! u6 V8 Y# N: g上面就是我们这个工程的所有源文件,我们在终端使用 gcc 编译这个工程,在终端输入“gcc main.c
; B) V. N" r5 W  B' F) ocalc.c -o main”,该命令的意思是用 gcc 对 main.c、calc.c 进行编译,然后输出可执行文件 main,运行% y3 p% U6 h0 S  O" @- v# w
结果如下图所示:
5 s/ o2 z: |$ I4 v5 M4 i" N# ]
- ?: B3 j; h  V  l通过上图可以看到生成了可执行文件 main,我们在终端运行 main 执行文件,运行结果如下图所示:
' Q/ m+ d/ j' }5 @) J" \ ( }* [# a- R. a$ ?& y0 Q
我们可以看到上图的运行结果和我们设计的结果是一致的。由于我们的这个工程是有三个文件,如果$ ~/ |% U5 \( y& g. a+ a
工程有几百个,几千个的文件,或者如果有一个文件被修改,使用上面的命令将会编译所有的文件,如果
/ ]+ s; g  q+ _; d' ]我们的工程有上万个文件,编译完一次工程所需要的时间就很可怕。最优的方法就是编译过一次以后,如
6 w' Y  E* X8 Q: {* \- z果后面在编译,只编译修改的文件,这样就会节约很多时间,因此我们修改下编译方法,命令如下:
# N* J7 P1 ^+ R) m; W) Q- \* p& Lgcc -c main.c. h, h8 e1 `$ \4 `* ~+ v! y; J
gcc -c calc.c
" @6 ]; A) \6 \: tgcc main.o calc.o -o main1 E. l: P$ T* p: Q- o
我们在终端输入上面的命令,结果如下图所示:
( J+ A& [& P5 L0 w% B & @  q9 j! S# v& J* X* C& {" z
上图的第一条和第二条命令里面使用了参数“-c”是把 main.c 和 calc.c 编译成对应的.o 文件,最后
3 G( F8 E; `( h+ \一条命令是把编译生成的.o 文件链接成可执行文件 main。假如我们修改了 main.c 这个文件。只需将 main.c
' R- K1 y! B* @5 O- E这个一个文件重新编译下,然后在把所有的.o 文件重新链接成可执行文件,对应的命令如下:4 I3 a0 @: l' ~
gcc -c main.c/ H3 X# X9 m, E; u
gcc main.o calc.o -o main
- [! }/ S% ?7 n! i可是这样还有一个问题,如果需要修改的文件有很多,这样工作量也会不小,所以我们需要一个工具:# b8 [" S$ l( _/ u4 W& D3 D
1.如果工程没有编译过,就会把工程中的.c 文件全部编译并连接成可执行文件
6 q7 _, z: |. I( T9 Y) O  l2.如果工程中有某些文件修改了,只编译修改的文件并连接成可执行文件
" A% ^) g' D+ G" r3.如果工程中的头文件修改了,那么就要编译所有引用这个头文件的.c 文件,并且连 接成可执$ S% e4 B3 x/ v+ [. y
行文件" T; Z1 |2 B( Y' ?1 q
我们开头说的 Makefile 就是完成这个功能的,下面我们在工程中建立一个 Makefile 文件来实现这样的功) I  @& V' U1 F
能(注意:文件名字必须为 Makefile,大小写是区分的)。我们使用 vim 创建 Makefile 文件(Makefile- S. w$ d' c4 ~
和我们的 main.c、calc.c 在同一级目录下),然后输入下面的脚本:
3 @5 ?. D. ^1 u$ ^: e# xmain:main.o calc.o5 j% n& I' Y: }, i/ ^
gcc -o main main.o calc.o
+ r$ E) J$ e% h; p  hmain.o:main.c' G: ~3 ]$ T& Q6 f. A
gcc -c main.c
  P9 j8 c$ x: o. t0 G& C# q( scalc.o:calc.c1 ^6 k& t; A. B/ o' ^3 W4 O
gcc -c calc.c. q" r' j( |- ~) \
clean:
! v" H6 V3 F& X, nrm -RF *.o
1 G- c: q; a3 T$ hrm -rf main
) T1 Q' {+ r2 `+ L! R$ @上面脚本缩进的行需要使用“Tab”键缩进,不要使用空格,这是 Makefile 的语法要求,编写完成的脚本, K: L0 y" }' p8 {) H
如下图所示:
: K2 H( _) q' g  s + }& Z9 X9 C7 T5 R) V- u2 o
编写好 Makefile,保存并退出,然后我们在终端输入“make”命令来编译我们的工程,make 命令会在
" y) e8 @3 z' C当前目录下查找“Makefile”文件,如果存在的话就按照 Makefile 里面的规则进行编译,如下图所示:
+ `0 Z6 n# F) s7 @5 A, x; F, [
4 i1 X$ N& u' b3 h0 a6 X通过上图可以看到编译产生了 main.o、calc.o 和 main 执行文件,说明编译成功了。接下来我们修改0 G/ p0 ~0 K1 P: u8 m" b
下 main.c 这个文件,如下图所示:
% O: s5 a  q. X+ S) _ ! W; H4 }# ^6 G) i
然后保存并退出,然后在终端输入“make”再次编译下工程,如下图所示:
2 M* t2 u4 X2 f0 V9 B) K, B & `& f" }+ ]+ O
通过上图我们可以看到只重新编译了修改的 main.c,并最终重新链接生成可执行文件 main,我们在终
( y( J6 \6 m3 ]9 U1 U  Q& B端运行可执行文件 main,如下图所示:6 U9 o, ~8 _, i- F
: z0 |, y. q) [/ f: v) P2 q
从上图的运行结果可以看到最后的结果等于 10 了,和我们程序的设计结果是一样的。
. K2 L% o* a2 n; _! M4 A3.4 e Makefile 语法 语法
+ R8 Q+ ?: j2 G3 r& x; q5 ?/ b: A3.4.1  初识 Makefile+ f8 C/ w/ M$ `7 [0 v/ V: o; C, a) W  P
Makefile 文件是由一些列的规则组合而成的,格式如下:
' a( ]7 O/ v$ a/ C  ptarget(目标文件) ...: prerequisites(依赖的文件) ...
: A% A1 ~3 U. u6 h. b; j; Ncommand(命令)  w( X% C: J1 K9 n1 ]
...
3 b: F. R8 H2 F* c3 y: {& k9 a$ r...
' Y7 {; R% h4 [/ ]7 C比如 3.3.2 中写的 Makefile 的规则:
: k; R, a3 s' |main.o:main.c1 ^& h6 \- m3 p: V2 u7 D
gcc -c main.c3 p% f' A3 b% ~7 T
这条规则的 main.o 是目标文件(将要生成的文件),main.c 是依赖的文件(生成 main.o 需要的文件),
+ p# j5 e$ F) a  R5 Q, t“gcc -c main.c”是生成 main.o 需要运行的命令。e Makefile  中每行的脚本如果有缩进的情况,必须使用+ M6 M9 S% g. W6 ?7 I* W* W
“ Tab ” 键缩进,切记不能使用空格缩进(这是 e Makefile  的语法要求),大家一定要切记!3 V2 r) V% N& x! j! k
下面我们来分析一下图 3.3.2 章节中写的 Makefile 文件,脚本如下:$ z3 _- f8 p7 G: ^( z- u7 i
1 1 main:main.o calc.o; g# D! y! R1 c, l/ e/ K
2 2 gcc -o main main.o calc.o' f( `* H1 M9 v. F/ L* I4 e" F
3 3 main.o:main.c
2 g" b: f" i5 v5 {2 H- w9 U* U4 4 gcc -c main.c7 i5 d( k# X% k0 Q$ e' r) A
5 5 calc.o:calc.c1 z0 O; t  ~5 M5 ]
6 6 gcc -c calc.c
. H! O: ^/ Z2 e  r# v5 u. F/ C7 71 G' N) C9 o8 A# P8 A$ `5 H
8 8 clean:# @5 U2 b# Y& n) s/ `" N8 @0 ?
9 9 rm -rf *.o
! [+ K7 B; S& _( k$ k; X10 rm -rf main* ?  x$ ^/ d; B1 ]
该脚本一共有 4 条规则,1、2 行是第一条规则,3、4 行是第二条规则,5、6 是第三条规则 8、9、10+ g+ g3 n+ U# X, P- [
是第四条规则。我们在运行 make 命令的时候,会解析当前目录下的这个 Makefile 文件里面的规则,首先
3 Y7 I( M4 Z+ u解析第一条规则,第一条规则中的目标文件是 main,只要完成了该目标文件的更新,整个 Makefile 的功能
' o% |6 c: h! Z5 x) H就完成了。在第一次编译的时候,由于目标文件 main 不存在,则会解析第一条规则,第一条规则依赖文件3 z5 [9 g" \4 f: ^
main.o、calc.o,make 命令会检查当前目录下是否有这两个.o 文件,经过检查发现没有,然后 make 会在7 |( s: }% M. t
Makefile 中查找分别以 main.o、calc.o 为目标的规则(第二条,第三条规则)。执行第二条规则依赖的文
$ N- h, E3 L- A9 ~/ e' y' \! U件是 main.c,make 命令检查发现当前目录下有这个文件,然后执行第二条规则的命令“gcc -c main.c”, @: e9 w) U( u- V9 X! m* y
生成 main.o 文件。然后执行第三条规则,第三条规则的目标文件是 calc.o,依赖的文件是 calc.c,make+ y- ~6 V( o6 z9 n
命令检查发现当前目录下存在该文件,然后执行第三条规则的命令“gcc -c calc.c”生成 calc.o 文件,' j/ F# R8 g& _1 T, D$ x
至此第一条规则依赖的 main.o、calc.o;两个文件已经生成了,然后运行第一条规则的命令“gcc -o main( p3 K/ i; s8 _- Y; S. E
main.o calc.o”生成 main 文件。因为 make 命令运行的时候会从 Makefile 的第一条规则开始解析,然后, r. U+ s5 ]. A; M: T3 `/ ^
根据第一条规则的依赖文件去遍历文件中的“对应规则”,然后在根据“对应规则”的依赖文件去遍历“对
% p6 Z& {" c1 z1 S( {/ `0 j" D应的规则”,采用这样递归的方式会遍历出完成第一条规则所需要的所有规则。下面我们来看看第四条规
  j; h# Z1 _) T$ \2 T& r2 ^4 P则的目标文件是 clean,我们通过查看发现该规则与第一条规则没有关联,所以我们在运行 make 命令的时2 a& N: o: D/ ^3 P) n8 E
候,不会遍历到该规则。我们可以在终端输入“make clean”命令来运行第四条规则,第四条规则没有依: n- l$ Y, ?9 d9 Z( y
赖的文件,所以执行运行命令“rm -rf *.o”和“rm -rf main”,这两条命令的功能是删除以.o 问结尾的
( h8 A! [/ B0 g( a5 r% c5 p所有文件,删除文件 main,运行如下图所示:
$ Y0 U$ j; m' ^9 H* Y+ W4 [% _ + C2 R! e! R5 E2 x0 G* f* ?/ r
通过上图可以看到 main.o、mcalc.o 和 main 三个文件已经删除了。通过该规则我们可以清除编译产生6 Q# ?; {" }1 Z, M5 {; U6 j( s
的文件,实现工程的清理。
% K* ?$ o4 E0 X+ M我们再来总结一下 make 命令的执行过程:9 M8 w1 H) X0 |; r4 \
1.make 命令会在当前目录下查找以 Makefile 命名的文件+ M+ R7 _+ ]* T$ H+ w* a: z" @
2.找到 Makefile 文件,就会按照 Makefile 里面的规则去编译,并生成最终文件" A) \+ r9 v$ J* ~0 ^
3.当发现目标文件不存在或者所依赖的文件比目标文件新(修改时间),就会执行规则对应的命令来更新。: h! D2 E+ B% Q( O$ j, p* O, M
我们可以看到 make 是一个工具,他会通过 Makefile 文件里面的内容来执行具体的编译过程。
% z3 N, ~+ V$ D+ L3 d2 Q2 l8 S3.4.2 Makefile  的变量4 ~% _4 }8 ~) k% j
在 3.3.2 章节中的 Makefile 第一条规则:
. B3 O8 X+ \1 T2 s% R; omain:main.o calc.o
2 @: C6 G. y5 y4 _; w9 Igcc -o main main.o calc.o0 t9 ]& I' N7 |; [5 p5 k9 q
在该规则中 main.o、calc.o 这两个文件我们输入了两次,由于我们的额 Makefile 文件内容比较少,
4 h" \" C4 N7 X4 n# W如果 Makefile 复杂的情况下,这种重复的输入就会非常占用时间,而且修改起来也会很麻烦,为了解决这# M$ d# M1 c0 C4 o! ~% f
个问题,Makefile 可以使用变量。Makefile 的变量是一个字符串。比如上面的规则我们声明一个变量,叫) a6 W9 O$ ~9 u$ l  n# V
objects,objs 或者是 OBJ,反正不管是什么,只要能够表示 main.o、calc.o 就行了,我们修改上面的规
; B& e( V6 i8 z( }
1 @) c8 z1 r7 S/ o6 i( T1 1 objects = main.o calc.o, b  G% }2 ^6 e% k
2 2 main( objects)
5 [; s0 N& F! q0 |) m* e6 S3 3 gcc -o main $( objects)8 ~. m8 ^6 s0 g7 l! c' I
我们来分析下修改后的规则,首先第一行是我们定义了一个变量 objects,并给赋值“main.o calc.o”,
2 i) O* c$ I" Q8 i: V! S第二行、第三行用到了变量 objects。Makefile 中的变量引用方式是“$(变量名)”,变量 objects 的赋值
  ]" Z! T/ \& g" P# r* g5 e6 ^使用“=”,Makefile 中变量的赋值还可以使用“:=”、“?=”、“+=”,这四种赋值的区别如下:
0 k5 r7 Y6 [8 j7 w6 E' c1. “= = ” 赋值符
; f- v+ t, E: S4 c1 B1 b0 E: X我们先在用户根目录的 work 目录下创建一个 Makefile 脚本,输入下面的内容:
/ W6 ^. ]1 g# T, d6 y' g1 ceshi1 = test
: z' [" j# O) t2 ceshi2 = $(ceshi1)3 O- l( u8 \3 c$ C1 H' h5 F
3 ceshi1 = temp
, r, _2 o- K' y* {4
* @4 z( `7 V+ O9 U. k+ r6 }  R4 T* i5 out:& A. e2 y; o+ O8 B
6 @Echo ceshi2(ceshi2)1 i+ }5 q' B8 N
第一行我们定义了变量并赋值“test”,第二行定义了变量 ceshi2 并赋值变量 ceshi1,第三行修改变量& P( [+ B/ l2 P+ S) a  [
ceshi1 的值为“temp”,第五行、第六行是输出变量 ceshi2 的值。我们在终端输入“make out”命令,如8 u( b& ^$ ]- U) L% A! Q
下图所示:# q# _$ N. Z$ U' ^$ A/ y+ O" r

& l- T1 d& Z9 W" T' R在上图可以看到变量 ceshi2 的值是 temp,也就是变量 ceshi1 最后一次的赋值。6 F( ?* \3 b% o7 v5 z4 Y, G. M
2. “ := ” 赋值符8 f1 \& r3 @' s0 h  o2 I; w7 u
我们修改“=”赋值符中的代码,第二行的“=”改成“:=”,代码如下:
- q7 z- I6 |6 ]! u# k% a" L1 ceshi1 = test) G5 x1 x: V; V1 E
2 ceshi2 := $(ceshi1)
$ ]# o" N, ]1 g% ^% l1 \4 B* z. }3 ceshi1 = temp2 O$ }! b$ y& g& i, r% G
4
* N3 t/ f) [# G7 j/ M* V6 x% V5 out:
' U+ D, ~; K' M. ^. q& z: R6 @echo ceshi2(ceshi2)4 J" d5 t5 z5 J* L: U6 |' w
我们在终端输入“make out”命令,如下图所示:
: Q7 P: V: [$ O- s" \
8 V9 Z+ }' q3 p我们可以看到上图的运行结果输出变量 ceshi2 的值是 test,虽然在第三行我们修改了变量 ceshi1 的
/ r- H: M* k7 `5 ~值,通过本实验我们可以看到“:=”赋值符的功能了。- O" p& W# \3 c" D- Y
3. “ ?= ” 赋值符" F+ G! M2 d! [3 c
ceshi ?= test
. _7 z& U" n( t“?=”赋值符的作用是如果前面没有给变量 ceshi 赋值,那么变量就赋值“test”,如果前面已经赋值了,: }/ _/ E* D, z+ k8 p! [/ i2 h  ^  u
就使用前面的赋值。1 y( u% s7 B, G6 j5 ]
4. “ += ” 赋值符3 V' M. x( E+ D& x. [* V' w! G
objs = main.o- H9 B8 o# Z! o. |( e, s: w4 ^  Y
objs += calc.o8 O$ j3 F, v! E2 M7 v* T' q# H
上面的脚本最后变量 objs 的值是“main.o calc.o”,“+=”赋值符的功能是实现变量的追加。
/ q7 z3 H3 |* v1 t3.4.3  条件判断
' @- l, q2 D+ F! @使用条件判断,可以让 make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量
7 s5 j# |: p! [. u, O的值,或是比较变量和常量的值。其语法有下面两种:
0 F9 f! {) o& \* I! s: _3 V3 ^1.4 x3 \: v7 w# F' b$ Y
<条件比较>. ]8 U2 w0 _, y( L, J
[条件为真时执行的脚本]
! O. k) a5 X" t& A" M2 C  Bendif6 B" ]- p+ T: b* Z% K" T$ S
2.
8 z. V% K, ?% {& B<条件比较>- @* `, g& C. q& P( c8 x9 d
[条件为真时执行的脚本]
" _& L+ t4 L- H7 ^3 S4 ]else1 v: k3 W  w6 z. g! f: ?
[条件为假时执行的脚本]
' h$ K. c, B8 kendif
" @- `5 I7 \9 G1 O8 F5 q条件比较用到的比较关键字有:ifeq、ifneq、ifdef、ifndef。2 n4 V( L( M; z& Z0 Y# w
ifeq 表示如果比较相等,语法如下:
! D1 a3 a# H4 m1 x# k7 v3 j7 Kifeq(<参数 1>, <参数 2>)
8 }5 }3 V; h1 S7 yifneq 表示如果不相等,语法如下:
4 f9 N0 f$ K, l5 w1 U  Kifneq(<参数 1>, <参数 2>)
/ N1 g$ b# [( G& \# Y4 A: Iifdef 表示如果定义了变量,语法如下:& K, g& i3 L5 ^5 A
ifdef <变量名>
* c2 S$ W9 g& nifndef 表示如果没有定义变量,语法如下:3 ?7 m+ {# E1 I" `) P; C5 y: \3 W
ifndef <变量名>
# [4 w/ t7 b2 I. M3.4.4  使用函数$ [$ j- |, V5 a5 t& B) t7 c
在 Makefile 中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make 所
: N( Q) T: G$ R8 ^* i( g支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。$ l/ J6 t# o" d4 e
函数的调用很像变量的使用,也是以“$”来标识的,语法如下:6 N) K4 U: I/ w0 v6 ~% E0 Y
$(<函数名> <参数集合>)( ^  O' N$ p+ K$ E( ^( C
或者:1 ]9 R* @) w3 |! p: l3 r
${<函数名> <参数集合>}- d; F% z# G0 C8 g1 K+ |
函数名和参数集合之间以空格分隔,参数集合的参数通过逗号分隔。函数调用以“$”开头,以圆括号或花$ e' G! k' M% m6 W7 [& q
括号把函数名和参数括起。感觉很像一个变量。函数中的参数可以使用变量。为了风格的统一,函数和变
0 _& T$ Q- J5 U量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。+ `  S8 A' l: g( a" ?% V9 L; G
因为统一会更清楚,也会减少一些不必要的麻烦。
3 Q2 ]& ^+ l0 T3 Z6 J接下来我们介绍几个常用的函数,其它的函数可以参考文档《跟我一起写 Makefile》。
  @' `: T' e5 H. W5 N+ `9 m6 j7 Mt 1.subst  函数. a" [. D7 {$ ?& h& M: Z
$(subst ,,)
3 W5 m# E+ q) U: a& g, v  I此函数的功能是把字串中的字符串替换成,函数返回被替换过后的字符串。如下示例:6 e3 R8 t0 G- Y, S
$(subst ee,EE,feet on the street)
- C, o/ [, t( ]+ L) d$ o! m  ~) y以上脚本实现把字符串“feet on the street”中的“ee”字符串替换成“EE”字符串,替换后的字符串
" `+ m7 o/ L( A: c& Y/ b2 `) a为“feet on the strEEt”。" r! ~. [4 s' o# `/ M
. 2. t patsubst  函数0 _$ ]" Q. A  W* S
$(patsubst ,,)
3 v& B3 ]' p! u6 [此函数的功能是查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式2 ^7 z0 ]' E3 S
,如果匹配的话,则以替换。这里可以包括通配符“%”,表示任意长
+ ]7 ^: S  ~3 {& q9 \- p6 x( D/ V度的字串。如果中也包含“%”,那么中的这个“%”将是中的那个
5 O. g) o& }( E3 C4 v“%”所代表的字串。(可以用“\”来转义,以“\%” 来表示真实含义的“%”字符)。函数返回被替换! _* {. X/ n, y
过后的字符串。如下示例:
6 G  B# r6 p9 V+ O; ?6 t$(patsubst %.c,%.o,x.c bar.c)
7 N) L7 W/ f) w) v" I以上脚本实现把字串“x.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.o bar.o”
1 y1 a0 Y# F/ O* o0 s( Yp 3.strip  函数, g) T( c& u1 t% A7 \) I  e% m
$(strip )
; J# p' L* a- o& F; s. u, `此函数的功能是去掉字串中开头和结尾的空字符,函数返回被去掉空格的字符串值。如下示例:
1 _& T: h& K- e$ c; o$(strip a b c )5 r; a: E+ V- |& d( c
以上脚本实现把字串“a b c ”去掉开头和结尾的空格,结果是“a b c”。
, Y" t% [! f4 P6 S5 |- I9 j. 4. g findstring  函数: |+ P- a6 f) z  Y  T+ H& i
$(findstring ,)
: N. i8 b! ]5 C: t: s此函数的功能是在字串中查找字串,如果找到,那么返回,否则返回空字符串,如下示1 s$ r8 Z! W, ]9 n6 R, u( u
例:# A# a$ A5 e+ e2 s& O7 p$ i; B
$(findstring a,a b c)# R6 g5 g- r5 L. K" T
$(findstring a,b c)
' Z5 Z+ J; N7 L! A0 D以上脚本,第一个返回“a”字符串,第二个返回空字符串。
" V" u  ?' n! Lr 5.dir  函数: _9 W) A1 u5 N
$(dir )# \% M* n6 T  G% e2 d
此函数的功能是从文件名序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部' v* I- \0 l4 O1 }$ u9 D
分。如果没有反斜杠,那么返回“./”。返回文件名序列的目录部分,如下示例:4 A; r& }( H4 r; n
$(dir src/foo.c hacks)
+ l" P, t0 P6 v" }, u以上脚本运行结果返回“src/”。0 k0 b# i- Z3 ]. _7 i
. 6. r notdir  函数
7 z5 B1 b! s: z& x8 i$(notdir )# k, w6 [% M& Q0 p
此函数的功能是从文件名序列中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后: f8 }! U/ W+ {$ y* u; L2 X
的部分,返回文件名序列的非目录部分,如下示例:4 y8 P  V8 B* i; {& C# b1 V* I
$(notdir src/foo.c)6 q0 \2 Y& K" W& v/ }* {
以上脚本返回字符串“foo.c”$ u0 ?( F( _9 @: U1 N( Y7 F$ I
. 7. h foreach  函数
" E( @) z! j/ O0 U+ W$(foreach ,,); d  q0 O. V2 a# _0 U
此函数的功能是把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含! p$ w/ `! J/ n' ~
的表达式。每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,
; J+ B% L# |: U9 L- q$ O( |' n最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函4 [* J& f& T/ L# {( H: S7 M8 _, `
数的返回值。所以,最好是一个变量名,可以是一个表达式,而中一般会使用这
' p" m+ T" k$ ~6 B" [个参数来依次枚举中的单词。如下示例:
5 t- P1 J' D8 e6 O, }names := a b c d
" v+ S4 e3 B! T! e# yfiles := $(foreach n,$(names),$(n).o)! b0 V) f# A! j  ^+ p
以上脚本实现$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出
/ L1 E8 W2 q) @/ `, K1 x一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以$(files)的值是“a.o b.o c.o d.o”。
# T: b# n. ?8 ?# w3 j(注意,foreach 中的参数是一个临时的局部变量,foreach 函数执行完后,参数的变量将不
0 u3 U: r6 v' f3 t6 P- J7 C在作用,其作用域只在 foreach 函数当中)。
2 b/ X! F  x: z+ C% F; V更多内容关注迅为电子( S* O" k: ~- {* t( w
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-11-24 09:41 , Processed in 0.203125 second(s), 28 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表