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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
我们在 Windows 下使用 C 语言开发的时候,一般都会有支持 Windows 的开发工具,比如我们学习 51 单
" l  ]) I/ N9 k8 v. m片机或者 STM32,所使用的的 Keil 开发软件。此类开发工具一般都会集编辑、编译于一体,我们只需要编辑
, |* Z7 q$ l: x! t) p0 q' x! v好代码,电机开发工具的编译按钮,就可以自动为我们编译出可执行的二进制文件了。Ubuntu 下的 C 语言
: ?" |; r( k& `开发与 Windows 下的 C 语言开发是不一样的,并没有图形界面的开发工具,而且编辑和编译是分开的。我. W. N2 a5 }& _9 v+ ?
们需要使用文本编辑软件先编写代码,然后使用编译工具(GCC)来编译,最终生成可执行的二进制文件。
/ N6 @9 N, Z- G; k4 c7 D; i如果我们的工程中有多个源文件,在编译的时候我们通常会通过 Makefile 文件来管理整个工程的源文件。5 d& C# T3 x- {% [5 O) ^+ G; p
本章我们来学习如何在 Ubuntu 下进行 C 语言成的编辑,GCC 编译,Makefile 文件的使用。通过本章学习我! n, p( q3 a, w5 W: u; H
们可以掌握 Linux 下的 C 语言开发基本方法,为后面的学习做好准备。$ \8 L# x3 v3 [( ]) \$ b# B
3.1 x Linux  下编写第一个 C C 程序 程序
+ v* o7 @2 \* g; B3 f' t本章节开始的部分我们介绍了 Ubuntu 下 C 程序开发分成两部分:编辑和编译。Ubuntu 下有很多种文本+ b" ~: _3 A" V& t2 D
编辑的工具,如 vim、Gedit、Emacs,这里我们使用前面章节介绍的 vim 文本编辑工具来编辑 C 程序。相信9 S. T6 x% p' m: o6 N& _
大家在学习 C 语言的时候都是从“Hello World”这个程序开始的吧,下面我们通过这个程序,来学习下 Linux
2 i) L7 }2 K/ x0 z下 C 程序开发的流程。
$ ~# q7 {3 R  J- k+ B; t3.1.1  代码编写  @* T1 p% P7 [
首先我们在用户根目录下建立文件夹“work”,用于保存所有 C 语言的程序,运行结果如下图所示:
5 Q* h$ ~% B4 ^" u  q0 G1 y, ~
; N: @; ^8 d$ s* A; w然后进入创建的文件夹 work,为了方便管理,我们每个例程都创建单独的文件夹,首先我们创建文件
* O; a& O/ y3 M2 f. ^1 ~. y7 r夹“hello_world”来保存我们的第一个 C 程序,如下图所示:
2 F5 L7 Q( T! Q! V! |& Y9 f
! s! U! |7 p) o: [9 z) H9 o然后进入上图中的 hello_world 文件夹,使用 vi 命令新建文件“main.c”,然后在里面输入下面的代
4 q( c) k' ?8 O( j码:
+ W; H) s+ x8 c; h) S#include
' T% }* ~: o2 Z+ wint mian(int argc, char *argv[])# c& V. H; _8 X1 k5 C7 O
{
9 k# ]& D, h4 B- \% Rprintf("Hello World!\n");9 {# |- E& c% Y' f) b! `: F
return 0;
$ g6 ~" [! G  x% `) x9 ]* \}  [0 J6 e" J2 O: a! w; H
编写完以后保存并退出 vim 编辑器,然后可以使用 cat 命令查看代码是否保存成功,如下图所示:2 R8 T) Z7 u8 |
8 u6 n0 [% {* V$ }
通过上图可以看到代码已经编辑完成了。
6 n7 _, k; F- B! A3.1.2  代码编译  R* i1 r* a( b  x% Z
Ubuntu 下使用 gcc 编译器来编译 C 程序,我们在安装 Ubuntu 系统的时候,gcc 编译器 morning 安装好
- |/ Z" E9 e7 M  S了,我们可以在终端输入“gcc -v”来查看下 gcc 的版本,如下图所示:2 r. n! O  p' O! _  ^" B# O
* u. k4 I' d. n. |* b! K
通过上图可以看到 gcc 的版本信息,说明我们的 Ubuntu 上已经安装了 gcc 编译器了,下面我们来看看; M  I5 ~1 M, w
怎么通过 gcc 编译我们的第一个 C 程序,我们在终端输入“gcc main.c -o main”,然后回车,会生成 main 文; Q1 D1 _/ z5 ^# V5 Z
件,如下图所示:
9 F& k' y( s0 B5 Z 6 i2 h4 @1 x: g+ \  t/ ^) r
在上面的“gcc main.c -o main”这个命令里面“-o“用来指定编译生成的文件名字,我们指定的是 main,- ?9 x/ M( v8 b! ]* ~
所以在图 3.1.2.2 中可以看到生成了文件“main”,如果我们想生成其它的文件名字,我们只需要修改“-o”% h9 P, X0 c) K; m' K. `1 @
后面的 main(修改成您希望生成的文件名字)。经过前面的步骤已经生成了可执行文件 main,现在我们演+ t5 U2 q/ G8 i
示下在终端如何运行一个可行性的程序,我们直接在终端当前目录下(可执行文件所在的目录下)输入8 R; v6 q9 ?, \. z% F' }
“./main”,然后回车,就可以运行可执行文件 main 了,运行结果如下图所示:! }3 d' n3 g1 b7 `# p
; o5 J  @2 c  I" c3 B
在上图中运行的命令“./main”,其中的“./”代表当前目录下。我们可以看到在终端打印出了 Hello
% k7 z: m, g1 f5 D. |9 ~6 O( G! `World!。至此,Linux 下的 C 语言编辑和编译的一整套流程我们就介绍完了。
: Q6 X* h9 @. P7 @% v3.2 c gcc 编译器 编译器
1 R9 V" Z9 h/ @# c& E7 u3.2.1 gcc  命令分析
, d+ T) `! A4 z; v8 a: }' p在 3.1 节我们已经使用 gcc 编译了 Linux 下的第一个 C 程序,gcc 命令的格式如下:
, f4 X( y% Q+ V5 N' w' Qgcc [参数] [文件名]- m6 L. g$ K/ V! Z7 ?7 c" C
主要参数说明如下:
( {* h5 r# N3 R: `8 L- ^- G-c 编译、汇编到目标代码(.o),不链接成可执行文件
6 j: O4 ?3 \5 K: P0 B-g 生成调试信息9 d- h: ~+ L, x) h. T& @" ~
-o 编译完成后生成的文件名,如果不使用该选项,默认生成 a.out 文件! O& i6 X$ [) j! m: w" L
-O 对程序进行优化编译,产生的可执行文件执行效率高, S! Q, R" I5 b( v
-w 不生成任何警告
5 ]% J5 `3 B4 @3 X5 k8 ]- V-S 仅编译到汇编语言,不进行汇编和链接
5 l- V6 S/ t! E! C7 F8 U* b3.2.2  编译警告错误处理  G, N8 e4 {' Y1 w4 C
我们是 Windows 下使用 Keil 或者其他开发工具,在编译的时候,如果程序有错误,开发工具会提示出4 E5 r& t# x' M0 v7 ?1 U' B" ~
具体的错误信息,可以很方便的定位到问题点,快速的修改出现的问题,gcc 同样也有类似的功能,下面我6 x+ e% m0 L; {( W
们来看下 gcc 的错误提示功能,首先我们在 work 目录下建立文件夹“test2”,然后使用 vim 在 test2 文件
; @. X1 U1 F, A$ {: h夹创建 main.c 文件夹,在 main.c 文件输入如下代码:0 p& M* U/ Q. g1 d# r) w6 c3 Y
#include ; H- D' R& e  w: G) f
int main(int argc, char *argv[]); P& w8 V% W; Y6 {3 }
{* N6 a9 s  q  R' Q# `! b, j
int a;
" C  ?: J# ~. c8 u9 Ha = 1
+ O( p0 K3 ]3 ?. o: Yprintf("a=\n", a);
% ^% J/ X) a" U8 Q# O" V6 Z1 greturn 0;
0 C+ \! R4 i0 \* ~}
: q3 A: |3 F7 q上面代码有两处错误:
7 A' I6 R/ `. N* I3 |! P8 A第 7 行 最后缺少“;”
' W9 Y% r& J5 V' K% e( D5 T% |/ `第 9 行 printf 语法不对,应该为:printf("a =%d\n", a);/ u6 m* }0 n, Z+ F% U
我们使用 gcc 编译 main.c,可以看到 gcc 会提示错误信息,如下图所示:5 o4 Q* V, t) K" p3 v7 y+ A

' o' F' L8 a+ X/ x) B3 Y+ A从上图中可以看到在 mian.c 文件的第 9 行 printf 前面缺少“;”我们在第 7 行“a = 1”后面加上“;”," y$ J( s) N+ P! \
然后继续编译,如下图所示:
3 e. b! [* z$ f( P8 y& A % e) X% a- A* T- v( a# C
从上图可以看出编译提示语法格式不对,我们把第 9 行修改成“printf("a=%d\n", a);”,然后在继续编
0 n# D' A5 J* L/ M; [4 `译,如下图所示:/ M4 F# E% ^" I* B
' ~# e$ ?. S0 V( Q0 }
我们可以看到这次 gcc 编译通过,最终生成了文件 main。我们在终端执行 main,运行结果如下图所示:
  n7 w: g" O# S' S- { 7 w- l" ~* G  y% n
从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出' g1 }9 i7 n& a# e* Q4 g
程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。  ~8 F1 M9 @* x1 B3 A
从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出
' f0 ^' \& u9 w1 D) I: b6 ^程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。3 {% v% a3 u  w2 e: S; V  _
3.2.3 gcc  编译流程
% k! k5 j  L' }8 A% Egcc 的编译流程可以分成四个步骤:
. k* J* o/ X) ?; y! F- y1.预处理,生成预编译文件(.文件)
/ T: M9 l1 A% y; |5 X4 V2.编译,生成汇编代码(.S 文件)5 W) Q$ b) @! T- i1 p; {5 a
3.汇编,生成目标文件(.o 文件)  w& N2 L! m" B- M
4.链接,生成可执行文件
/ }& n% b$ P# S' W- z: a3 3.3  初识  Makefile% Q7 d4 N& p: X& g+ R( n+ T
3.3.1  什么是 Makefile& P# c. N1 y9 V' o7 m
在 3.2 章节我们了解了在 Ubuntu 系统下通过 gcc 编译器来编译 C 程序,在我们演示的历程中只有一个
; d9 e0 A+ T/ }& c. d; o" wC 文件,我们直接在终端输入 gcc 的编译命令,就完成了 C 程序的编译。我们在实际开发过程中,如果我们% Q. w1 s9 i- Q1 C5 m" z
的工程有几十个,或者几百几千个 C 文件,我们通过在终端输入 gcc 命令来编译,这显然是不现实的。为/ o' T2 l, c% ^* _- W) e6 v6 B
了解决这个问题我们可以使用“make”命令,它会解析 Makefile 文件中的指令(应该说是规则)来编译整
+ v% `; U4 l, \个工程。在 Makefile 文件中描述了整个工程的所有文件的编译顺序,编译规则。& B. o4 R/ x2 b% z4 [5 _1 M
作为一个专业的程序员,一定要掌握 Makefile 的,我们可以通过 Makefile 能了解到整个工程的处理过程1 Q" }: D2 s. ]
的。
9 I+ P) x/ ~, X" W- @由于Makefile涉及到很多的知识点,以至于可以单独写本书来讲述,所以本章我们只是讲解下Makefile
. n" ~. h+ A5 M8 H* y) F的基础入门,如果详细的研究 Makefile,可以给大家推荐《跟我一起写 Makefile》这个电子文档,该文档
- g: @& H3 }# s+ A已经放在了:i.MX6UL 终结者光盘资料\09_其它参考资料里面了。8 x9 n7 Z  J$ Z; P0 N
3.3.2  第一个 Makefile
8 `, q8 F# D; e3 }4 L, Z在本节我们建立这样一个工程,计算两个整形数的和,并将结果在终端显示出来。在这个工程中一共有8 ]( q% c2 r9 F7 ]' K+ f( e
main.c、calc.c 两个 C 文件和 calc.h 这个头文件。其中 main.c 是主文件,calc.c 负责接收 main.c 传过/ h% r" X$ p* x& f
来的数据,然后进行相加。main.c 文件的内用如下:
, K' _% ]: b1 g, O' @# |#include ( a5 G, c- `" ]& p5 D
include "calc.h"3 s+ }4 Y; D7 K( E% b
int main(int argc, char *argv[])4 O0 k, w. h6 d. Z! _3 W0 O
{) C9 i5 K# Z6 K* T+ G- K
int a = 3, b = 6, sum;% \& e1 A8 A6 a1 I9 {+ t# _" S
sum = calc(a, b);: L: b& `! Z9 c
printf("%d + %d = %d\n", a, b, sum);
3 o/ L+ c3 g" Z) ?7 yreturn 0;$ \6 x, x1 R- _& A
}. I( S; {0 U) }/ ^7 j# @* r* j
calc.c 文件内容如下:& u& q% W2 S" O" @
#include 1 Z* ?( C% s. O& e) b* W
int calc(int a, ing b)* p. E1 f* L& S. M+ F, g7 b: Y
{( k0 W0 m: C1 u  n+ h2 l! ?' H
return (a+b);
' o1 V! o9 S) c9 a& y# O! h6 }}% Y0 v  N$ W4 t3 D
文件 calc.h 内容如下:
+ Z* |, {$ g# z" z0 p#ifndef _CALC_H
8 i- w  X2 V$ `8 }- G#define _CALC_H
, @- }8 a5 p3 R4 y$ m$ S( X% }5 R0 nint calc(int a, int b);
; H, G' j7 ^* F% e; P#endif
& r6 L+ ?1 d# z: V上面就是我们这个工程的所有源文件,我们在终端使用 gcc 编译这个工程,在终端输入“gcc main.c  U' e+ E6 h2 k9 ?* u6 x
calc.c -o main”,该命令的意思是用 gcc 对 main.c、calc.c 进行编译,然后输出可执行文件 main,运行7 Q% i1 |% }" q$ t0 x$ ~
结果如下图所示:7 B6 n( b9 V  H* L$ z
2 _# y# j9 N# B: w* i/ X+ x9 U
通过上图可以看到生成了可执行文件 main,我们在终端运行 main 执行文件,运行结果如下图所示:
+ q# I* A7 x' e0 c
3 L5 \. i2 P) Q6 ?, o  a+ ]我们可以看到上图的运行结果和我们设计的结果是一致的。由于我们的这个工程是有三个文件,如果
! G% o5 y5 _: |( F2 j工程有几百个,几千个的文件,或者如果有一个文件被修改,使用上面的命令将会编译所有的文件,如果
$ Z$ Z8 E& P% f4 E6 c我们的工程有上万个文件,编译完一次工程所需要的时间就很可怕。最优的方法就是编译过一次以后,如
3 D6 j3 l- p% [& s果后面在编译,只编译修改的文件,这样就会节约很多时间,因此我们修改下编译方法,命令如下:+ J' j. m0 ?1 ]4 P, G
gcc -c main.c) c) P0 C) y+ Y. }& x# r( }
gcc -c calc.c
; [# v) n0 D5 _: t  ~: tgcc main.o calc.o -o main
* ~! I! X7 Y: \+ P& `8 z我们在终端输入上面的命令,结果如下图所示:  u3 v( f* l+ W. z

# ?+ D; u  N1 ?7 I4 P上图的第一条和第二条命令里面使用了参数“-c”是把 main.c 和 calc.c 编译成对应的.o 文件,最后
9 s4 L- x9 Z9 w! F. Q! _( |一条命令是把编译生成的.o 文件链接成可执行文件 main。假如我们修改了 main.c 这个文件。只需将 main.c0 R# B8 q9 b! y$ Z, b9 ?$ i
这个一个文件重新编译下,然后在把所有的.o 文件重新链接成可执行文件,对应的命令如下:
4 G8 T. S. q, j1 q, {/ F8 O+ \gcc -c main.c
& p3 [8 R0 Q6 o6 Ngcc main.o calc.o -o main. n- T) h0 q+ T) o' e
可是这样还有一个问题,如果需要修改的文件有很多,这样工作量也会不小,所以我们需要一个工具:
- e; W& l+ D- d$ r% Q1.如果工程没有编译过,就会把工程中的.c 文件全部编译并连接成可执行文件) T) n4 y+ H! O) d3 ~, s+ ]; M
2.如果工程中有某些文件修改了,只编译修改的文件并连接成可执行文件
. J, U  V. l% s1 C, r3.如果工程中的头文件修改了,那么就要编译所有引用这个头文件的.c 文件,并且连 接成可执
# e/ {, Y9 {) |( s+ `6 R行文件
5 {5 N; }! i$ Y; }7 d我们开头说的 Makefile 就是完成这个功能的,下面我们在工程中建立一个 Makefile 文件来实现这样的功8 C* d+ i/ h, Y  ?# U& V4 F
能(注意:文件名字必须为 Makefile,大小写是区分的)。我们使用 vim 创建 Makefile 文件(Makefile3 y; x: T  {/ M  u! Y
和我们的 main.c、calc.c 在同一级目录下),然后输入下面的脚本:
& S5 E1 w0 f6 F+ I; w) jmain:main.o calc.o& Z! {* C5 T' z2 \4 t
gcc -o main main.o calc.o& L- s1 S2 d! g0 ?( n3 b& j
main.o:main.c# s* r+ B" G9 j& K; B9 |" {6 Y
gcc -c main.c
3 n9 [' `, V, _calc.o:calc.c+ a; s- p" ?9 l1 T
gcc -c calc.c1 k2 |) \9 S# k( X: P% F% g" A7 _. z
clean:
% ?+ _! b- Q, i$ brm -RF *.o
( P7 n9 O9 D; q! arm -rf main
) b( ~: L# S, S2 l, g# O上面脚本缩进的行需要使用“Tab”键缩进,不要使用空格,这是 Makefile 的语法要求,编写完成的脚本
, G5 ], `: t/ {7 i2 {8 r2 [2 }如下图所示:
% c% K$ v$ F/ |
0 y% `4 D0 A% R+ A% K+ E编写好 Makefile,保存并退出,然后我们在终端输入“make”命令来编译我们的工程,make 命令会在- m  Z3 S2 [! n/ J2 m# }
当前目录下查找“Makefile”文件,如果存在的话就按照 Makefile 里面的规则进行编译,如下图所示:0 A# d- e$ O  b- O3 ~3 ^% Y" p$ G
3 `3 t- W. k/ N0 m: [
通过上图可以看到编译产生了 main.o、calc.o 和 main 执行文件,说明编译成功了。接下来我们修改; J/ D2 w* B/ g
下 main.c 这个文件,如下图所示:. x7 B" J% C% N. Q/ [) L1 V
% @. K, I0 a& t5 w& f" F# ^, J
然后保存并退出,然后在终端输入“make”再次编译下工程,如下图所示:1 u/ j$ p3 {7 Q2 C4 C7 A9 R* R( U/ b

. j' {% x5 ~) W( \7 o( h通过上图我们可以看到只重新编译了修改的 main.c,并最终重新链接生成可执行文件 main,我们在终
6 I  N; ]' h$ \* b9 ~1 e2 N  A端运行可执行文件 main,如下图所示:
( I; F( d8 P/ J& c0 b$ P : a( f8 T% x: c' c; k
从上图的运行结果可以看到最后的结果等于 10 了,和我们程序的设计结果是一样的。
' x8 `: ~- ^' o3.4 e Makefile 语法 语法6 B5 [: r) c7 I9 t3 z7 f9 t4 Y7 {" w
3.4.1  初识 Makefile9 @: W$ W8 t  }1 [( p5 P( P! I9 v
Makefile 文件是由一些列的规则组合而成的,格式如下:9 F8 b% f' L! _4 U7 x9 o
target(目标文件) ...: prerequisites(依赖的文件) .... J- F" |( G3 ^
command(命令): P4 f$ p9 Z: J- B
...
4 J$ Y4 }5 W6 v9 i8 V...
2 ^7 ?  C7 e% i. @/ z4 V8 m  L比如 3.3.2 中写的 Makefile 的规则:
: }! {: J; i) z. m, Gmain.o:main.c+ Q  n/ }3 @# C7 }: d
gcc -c main.c$ V/ r5 E1 a4 U6 x9 g
这条规则的 main.o 是目标文件(将要生成的文件),main.c 是依赖的文件(生成 main.o 需要的文件),% d5 j0 M( {4 y0 p+ U
“gcc -c main.c”是生成 main.o 需要运行的命令。e Makefile  中每行的脚本如果有缩进的情况,必须使用" n6 d5 H6 o+ Z0 ^2 i
“ Tab ” 键缩进,切记不能使用空格缩进(这是 e Makefile  的语法要求),大家一定要切记!/ d) U$ r$ o2 ?( O% l( k/ }- t
下面我们来分析一下图 3.3.2 章节中写的 Makefile 文件,脚本如下:
# t5 f0 P( o( r& k8 {5 F+ P! `1 1 main:main.o calc.o
4 c2 h- R: Z+ y: F4 r# C( T' R3 C/ z5 C2 2 gcc -o main main.o calc.o* h$ s* z- @0 N2 D1 k; d' m9 y
3 3 main.o:main.c0 }; c& m+ g, q7 `  r
4 4 gcc -c main.c
9 k! T; e- g" G# w, H' l! M5 5 calc.o:calc.c
) |" j' _! m8 {9 \# }% r1 }6 6 gcc -c calc.c$ r: P: q. u$ S6 O; b+ g
7 74 |& L' b# x6 q) B6 R' P& e# O
8 8 clean:" Y5 e( N$ P* O7 b9 E8 L1 f! C
9 9 rm -rf *.o
* D/ N3 U2 T+ h* p% Z  S10 rm -rf main6 i6 z% q( f! }% f0 d7 u# D: T
该脚本一共有 4 条规则,1、2 行是第一条规则,3、4 行是第二条规则,5、6 是第三条规则 8、9、10; ^4 S  x) O& y1 C0 b; |
是第四条规则。我们在运行 make 命令的时候,会解析当前目录下的这个 Makefile 文件里面的规则,首先) S! \% g3 l& W' ]+ P
解析第一条规则,第一条规则中的目标文件是 main,只要完成了该目标文件的更新,整个 Makefile 的功能
/ D6 N! e2 c9 ]3 Z4 h0 w  J: Y就完成了。在第一次编译的时候,由于目标文件 main 不存在,则会解析第一条规则,第一条规则依赖文件
3 w9 K/ G6 N: B. A/ W$ amain.o、calc.o,make 命令会检查当前目录下是否有这两个.o 文件,经过检查发现没有,然后 make 会在
' j2 ], M( r9 o9 n8 n. zMakefile 中查找分别以 main.o、calc.o 为目标的规则(第二条,第三条规则)。执行第二条规则依赖的文
! E+ K- c5 P8 t1 }4 c" v- ]件是 main.c,make 命令检查发现当前目录下有这个文件,然后执行第二条规则的命令“gcc -c main.c”
- Y) t; E5 @! |* K8 }生成 main.o 文件。然后执行第三条规则,第三条规则的目标文件是 calc.o,依赖的文件是 calc.c,make
% L  d3 G0 V8 B3 Z" B. z2 P命令检查发现当前目录下存在该文件,然后执行第三条规则的命令“gcc -c calc.c”生成 calc.o 文件,
2 G% |% Y8 z2 P, k2 J7 p至此第一条规则依赖的 main.o、calc.o;两个文件已经生成了,然后运行第一条规则的命令“gcc -o main
  u1 q8 V' J/ T8 b, b, n+ gmain.o calc.o”生成 main 文件。因为 make 命令运行的时候会从 Makefile 的第一条规则开始解析,然后
* J9 n# m( y: q( F; F( w根据第一条规则的依赖文件去遍历文件中的“对应规则”,然后在根据“对应规则”的依赖文件去遍历“对4 n; `+ Y$ \: j0 Z
应的规则”,采用这样递归的方式会遍历出完成第一条规则所需要的所有规则。下面我们来看看第四条规; f1 A, C% e; D1 }  l7 T
则的目标文件是 clean,我们通过查看发现该规则与第一条规则没有关联,所以我们在运行 make 命令的时8 }$ U! J8 v9 e: x9 e5 Q% w
候,不会遍历到该规则。我们可以在终端输入“make clean”命令来运行第四条规则,第四条规则没有依
1 y: Z7 h) m  }( c6 |1 V" V+ b( A3 `赖的文件,所以执行运行命令“rm -rf *.o”和“rm -rf main”,这两条命令的功能是删除以.o 问结尾的
. p3 C" D: M0 v- {- B所有文件,删除文件 main,运行如下图所示:' F$ g9 j* d1 t$ T
6 P: T0 A" q# g- {1 r
通过上图可以看到 main.o、mcalc.o 和 main 三个文件已经删除了。通过该规则我们可以清除编译产生
. N# `4 i2 E  Y的文件,实现工程的清理。) ?7 c7 q# l( a4 v
我们再来总结一下 make 命令的执行过程:* ?7 I  Z" @) L0 p: _
1.make 命令会在当前目录下查找以 Makefile 命名的文件
+ E- _2 v5 ^( B: E2.找到 Makefile 文件,就会按照 Makefile 里面的规则去编译,并生成最终文件$ e2 m' [# s6 X6 T3 Z  d
3.当发现目标文件不存在或者所依赖的文件比目标文件新(修改时间),就会执行规则对应的命令来更新。
  S. \6 T: R% Q& k9 }我们可以看到 make 是一个工具,他会通过 Makefile 文件里面的内容来执行具体的编译过程。
8 t: F6 R- P+ J7 T! I( ^9 I3.4.2 Makefile  的变量
1 }/ t  M2 S. J' o( @# }. O! p在 3.3.2 章节中的 Makefile 第一条规则:
% M  j- @; y) b& m, m; Amain:main.o calc.o
% Q0 [3 l5 ~' c1 Egcc -o main main.o calc.o
# S8 J% V) n7 M/ x' Y  g& y, A; l在该规则中 main.o、calc.o 这两个文件我们输入了两次,由于我们的额 Makefile 文件内容比较少,. {  t2 y% Z8 D- V' q% o) E
如果 Makefile 复杂的情况下,这种重复的输入就会非常占用时间,而且修改起来也会很麻烦,为了解决这
- \1 K8 u) }2 r. e1 l1 K% S个问题,Makefile 可以使用变量。Makefile 的变量是一个字符串。比如上面的规则我们声明一个变量,叫  H; w$ X5 k- O# B/ G
objects,objs 或者是 OBJ,反正不管是什么,只要能够表示 main.o、calc.o 就行了,我们修改上面的规
; r0 V5 W; Z5 D3 b
, q" N6 e; N* R$ v1 1 objects = main.o calc.o
8 i# B  }+ ?+ G" k! K- c; M) F2 2 main( objects)
5 S3 J  D6 Z0 |3 B' x% p' c3 3 gcc -o main $( objects)' M* @& j3 D2 D0 k$ m$ c. C
我们来分析下修改后的规则,首先第一行是我们定义了一个变量 objects,并给赋值“main.o calc.o”,
. a( z9 }" q. A! K第二行、第三行用到了变量 objects。Makefile 中的变量引用方式是“$(变量名)”,变量 objects 的赋值' `/ o5 ~. F& k, p1 A
使用“=”,Makefile 中变量的赋值还可以使用“:=”、“?=”、“+=”,这四种赋值的区别如下:" h/ |/ I( u' v  \' V: k3 S, h5 o  O
1. “= = ” 赋值符' C& m& _# Q) c' k3 h
我们先在用户根目录的 work 目录下创建一个 Makefile 脚本,输入下面的内容:
) L$ a' D5 V$ i+ J% L1 ceshi1 = test, H/ d( f; S$ }( o: ]
2 ceshi2 = $(ceshi1)  h% Y7 S, }# Y3 |
3 ceshi1 = temp
: H# a2 x( E7 ~/ `4
, M* v* g  G" V+ f- P  \5 out:) j; B7 ^/ q1 y+ `, Z* L# A1 t9 W
6 @Echo ceshi2(ceshi2)/ @2 S6 q% h2 v" m
第一行我们定义了变量并赋值“test”,第二行定义了变量 ceshi2 并赋值变量 ceshi1,第三行修改变量
, m% z7 W8 d1 G! c2 ?3 Oceshi1 的值为“temp”,第五行、第六行是输出变量 ceshi2 的值。我们在终端输入“make out”命令,如
, O7 k# f1 Z0 U. f下图所示:) N2 i5 i/ b$ g& M

2 F/ ]7 C& `3 Y) f在上图可以看到变量 ceshi2 的值是 temp,也就是变量 ceshi1 最后一次的赋值。
: Z2 p; g9 ^/ O5 }* V1 w2. “ := ” 赋值符
) i8 U) d% x: Q! D7 ]我们修改“=”赋值符中的代码,第二行的“=”改成“:=”,代码如下:
) u: d4 Q2 m/ Y4 [) D8 T- ?& B9 _1 ceshi1 = test; U! j. I, a8 i% j+ ?1 W" P/ G
2 ceshi2 := $(ceshi1)* n5 U' r: R+ P( i* ?
3 ceshi1 = temp( a" j/ a1 F6 l: \  a3 L
46 @0 w+ u6 X# X# V2 m
5 out:- c' U5 a2 Z4 h2 n6 ~# E
6 @echo ceshi2(ceshi2)3 p4 z5 T( R' H  H4 T
我们在终端输入“make out”命令,如下图所示:5 }; h  i6 G; g
5 t& F! r2 ?1 n/ o. G
我们可以看到上图的运行结果输出变量 ceshi2 的值是 test,虽然在第三行我们修改了变量 ceshi1 的! g/ D) O& \) {3 z  W) `& _$ `
值,通过本实验我们可以看到“:=”赋值符的功能了。
+ s/ M* x+ U0 t" q. K3. “ ?= ” 赋值符
' o% B! q% O. E' bceshi ?= test" ^5 i8 p1 Z! S+ q- T6 s
“?=”赋值符的作用是如果前面没有给变量 ceshi 赋值,那么变量就赋值“test”,如果前面已经赋值了,
4 D+ m# m) f5 [- B# A3 Q4 }就使用前面的赋值。9 o$ i1 K) W7 N8 {1 m9 V( K9 H
4. “ += ” 赋值符$ S+ ?1 g0 w  Z/ T
objs = main.o! I6 H0 F2 [5 Z' x* U
objs += calc.o  ~& Q) j8 F/ I4 l
上面的脚本最后变量 objs 的值是“main.o calc.o”,“+=”赋值符的功能是实现变量的追加。4 Z, k; R2 ~3 A- ~( H) S; @; d$ `
3.4.3  条件判断
/ \, Q" D6 K; b- L使用条件判断,可以让 make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量1 p8 k* P, ?7 V
的值,或是比较变量和常量的值。其语法有下面两种:( |! m% D' ^( D, ^9 O) j
1.
' }5 U. W  B7 x/ _& c5 N<条件比较>
' ^6 M+ r1 i0 M9 c[条件为真时执行的脚本]5 G- W" a, o$ E" `: t6 R- T& q. S
endif
& Y# @$ y& T+ O- X  u1 _4 D2.3 f; ]% e/ @  e
<条件比较>, I! c7 N1 w7 `
[条件为真时执行的脚本]' r0 R7 Y! \; w3 r- Z  C& O7 M* @& U
else  g+ j4 H5 U# y2 o, W# @& L. W# q
[条件为假时执行的脚本]
2 L% h% B$ k. |endif5 f5 Q; @% k$ R' \
条件比较用到的比较关键字有:ifeq、ifneq、ifdef、ifndef。
+ C9 |& }1 w; U! uifeq 表示如果比较相等,语法如下:7 h' e- L5 x, l% B8 h) P1 ^
ifeq(<参数 1>, <参数 2>)/ {9 e5 [- _/ R
ifneq 表示如果不相等,语法如下:
5 d3 j! I' ^+ c, W5 Rifneq(<参数 1>, <参数 2>)3 h) \2 V' P6 I% j: f' t, B
ifdef 表示如果定义了变量,语法如下:
! i2 Y" V/ b7 gifdef <变量名>, q" c( C* d4 f  _& ~0 l# u( S
ifndef 表示如果没有定义变量,语法如下:6 H& l4 l. z; ~" ^2 w& w9 n
ifndef <变量名>
9 y+ J6 q- w( e9 e! H0 }+ W3.4.4  使用函数
  O6 P; ^" G( J( ?' X在 Makefile 中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make 所
" X- N0 q* ~) ^  s2 r支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。
8 a+ d0 f- o5 V5 E- ]函数的调用很像变量的使用,也是以“$”来标识的,语法如下:
/ S! T8 H8 I6 Z$ }* \% L% |- r* @! C, ~$(<函数名> <参数集合>)2 `5 v$ T# k& Y/ V& Z
或者:- _2 q5 C' u7 P2 r
${<函数名> <参数集合>}
3 C& a, G6 S4 j* k& b: n函数名和参数集合之间以空格分隔,参数集合的参数通过逗号分隔。函数调用以“$”开头,以圆括号或花
  h/ F. H4 }0 b, y; o" [括号把函数名和参数括起。感觉很像一个变量。函数中的参数可以使用变量。为了风格的统一,函数和变
3 W9 {. A/ I+ `, s% v4 Y- `# \量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。
- W7 S* h4 O4 w$ e9 ?4 f; U因为统一会更清楚,也会减少一些不必要的麻烦。
' s$ F" T% X: S/ N5 o6 u接下来我们介绍几个常用的函数,其它的函数可以参考文档《跟我一起写 Makefile》。
+ ~; m( \: i) X# tt 1.subst  函数* {) Y3 M6 ]' V5 ~: d' P
$(subst ,,)
! b; O( H+ G) v, o6 o此函数的功能是把字串中的字符串替换成,函数返回被替换过后的字符串。如下示例:9 C$ i+ {+ g' K" q. I% a' u
$(subst ee,EE,feet on the street)5 G0 ?1 k8 j2 I! Y4 d1 S
以上脚本实现把字符串“feet on the street”中的“ee”字符串替换成“EE”字符串,替换后的字符串
5 `* t' v2 N8 r( ]$ z" m! X0 D6 y为“feet on the strEEt”。
* g3 f/ Z! B5 n) r( B) U. 2. t patsubst  函数% S1 y6 ?- d9 N4 z+ z: u. ^
$(patsubst ,,)
: [# }5 Y* `: B; W9 [  J  `$ M此函数的功能是查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式# F9 Z( e" t/ W; v% r) c
,如果匹配的话,则以替换。这里可以包括通配符“%”,表示任意长
5 ?8 w, L# i* ?, S5 W# u度的字串。如果中也包含“%”,那么中的这个“%”将是中的那个
1 C1 v! G0 ~8 J“%”所代表的字串。(可以用“\”来转义,以“\%” 来表示真实含义的“%”字符)。函数返回被替换5 E' e+ R4 I4 Y, V0 P" V' y
过后的字符串。如下示例:  ]9 W$ F5 o1 o+ T# @5 m
$(patsubst %.c,%.o,x.c bar.c)
3 o; j6 C! _7 @& H以上脚本实现把字串“x.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.o bar.o”
2 K3 a: v" I8 x+ q6 B# Y& ^p 3.strip  函数
# v6 x$ |1 C; U, A' j4 n$(strip )' I7 U0 y9 ]9 B* ^
此函数的功能是去掉字串中开头和结尾的空字符,函数返回被去掉空格的字符串值。如下示例:
2 F8 R/ P# f  ~* B- l3 f$(strip a b c )
+ p( \$ Q6 H; ^  Y1 N* @8 K- U3 F1 ~& n! a以上脚本实现把字串“a b c ”去掉开头和结尾的空格,结果是“a b c”。
' `! j, |  j4 c3 |+ O; a, V. 4. g findstring  函数
; R: z6 J, |( P) [6 _6 }" c4 c# M$(findstring ,)
. b/ c( c* I" n6 a" k9 f; m9 y% X' w3 o此函数的功能是在字串中查找字串,如果找到,那么返回,否则返回空字符串,如下示
* i+ b" B- h/ A! |% a例:
4 ?1 N) w1 O$ H$(findstring a,a b c)4 N) N1 P' z2 n1 j+ |
$(findstring a,b c)
2 P7 X- H' j7 Z0 U以上脚本,第一个返回“a”字符串,第二个返回空字符串。7 W, H9 Q7 k/ {! L6 {( }; D1 s
r 5.dir  函数
2 Y  \8 H: R1 P3 i" Y$(dir )
: g9 E3 v( F4 [2 n此函数的功能是从文件名序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部
. S' [9 `7 }1 C2 @分。如果没有反斜杠,那么返回“./”。返回文件名序列的目录部分,如下示例:
: I: U" x* ]9 V6 W1 l9 w$(dir src/foo.c hacks)1 |  z2 {9 L1 a$ l* E$ W
以上脚本运行结果返回“src/”。
( Y1 K4 b" K, p; d5 w. 6. r notdir  函数" m1 O0 q3 u0 h" k- A
$(notdir )9 M8 d8 F  W( _8 P9 k3 d
此函数的功能是从文件名序列中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后' K6 ]  e/ _8 ?8 u2 h, M, N
的部分,返回文件名序列的非目录部分,如下示例:; l# w  e3 W3 `
$(notdir src/foo.c)6 [5 ?( ^0 x' F0 {) ]* o
以上脚本返回字符串“foo.c”. z# x, x) N, d+ N) L6 S' M
. 7. h foreach  函数
/ P2 p! y) Q' E' ~$(foreach ,,)$ m0 A- m2 j6 A* ~+ Y
此函数的功能是把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含
& B+ y4 x$ L, k6 E  J的表达式。每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,$ u- I( y' k3 e6 |+ v
最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函
6 |( z& d, l3 ?6 h数的返回值。所以,最好是一个变量名,可以是一个表达式,而中一般会使用这% p% F# L9 Q$ I' ]5 U8 ^
个参数来依次枚举中的单词。如下示例:" e/ f! f  [4 E2 t
names := a b c d
1 U- w5 H  g" v5 ^files := $(foreach n,$(names),$(n).o)# K& r* h' ?( j8 g3 Y8 O: ^
以上脚本实现$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出+ Q2 N- ]) U# c. V
一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以$(files)的值是“a.o b.o c.o d.o”。; Z! L5 ^  [/ h: n
(注意,foreach 中的参数是一个临时的局部变量,foreach 函数执行完后,参数的变量将不# V- {: L1 k9 Y# L0 N
在作用,其作用域只在 foreach 函数当中)。/ k  ~5 y) D$ P* s# }0 @( p
更多内容关注迅为电子4 B2 L# y2 f( b
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

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

EDA365公众号

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

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

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

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

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