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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
我们在 Windows 下使用 C 语言开发的时候,一般都会有支持 Windows 的开发工具,比如我们学习 51 单) o3 N" p! F0 _6 y
片机或者 STM32,所使用的的 Keil 开发软件。此类开发工具一般都会集编辑、编译于一体,我们只需要编辑2 e( P) J7 z1 [
好代码,电机开发工具的编译按钮,就可以自动为我们编译出可执行的二进制文件了。Ubuntu 下的 C 语言
5 `$ g* i, I$ Y# F6 p7 G开发与 Windows 下的 C 语言开发是不一样的,并没有图形界面的开发工具,而且编辑和编译是分开的。我
& N) j" V0 p0 p7 ?1 d1 f! m7 L们需要使用文本编辑软件先编写代码,然后使用编译工具(GCC)来编译,最终生成可执行的二进制文件。
. t% C% t4 o( v& o) f" g如果我们的工程中有多个源文件,在编译的时候我们通常会通过 Makefile 文件来管理整个工程的源文件。! J( J6 a/ ?1 {% l6 d" ]
本章我们来学习如何在 Ubuntu 下进行 C 语言成的编辑,GCC 编译,Makefile 文件的使用。通过本章学习我
5 x& T& q5 q; E- V3 H们可以掌握 Linux 下的 C 语言开发基本方法,为后面的学习做好准备。
6 R0 y: k4 h( y  z8 ?4 l0 o( k3.1 x Linux  下编写第一个 C C 程序 程序
1 H" U4 s3 ?& O7 ~本章节开始的部分我们介绍了 Ubuntu 下 C 程序开发分成两部分:编辑和编译。Ubuntu 下有很多种文本
0 M( u7 Q2 B0 A! q编辑的工具,如 vim、Gedit、Emacs,这里我们使用前面章节介绍的 vim 文本编辑工具来编辑 C 程序。相信9 a* S3 D' \9 ~2 G' {6 p$ z" M
大家在学习 C 语言的时候都是从“Hello World”这个程序开始的吧,下面我们通过这个程序,来学习下 Linux4 Y8 d' j$ j: A5 t1 Z- ~
下 C 程序开发的流程。
' E. v6 Y# [4 C: E7 O: D3.1.1  代码编写% ^1 J$ s, @; p, g
首先我们在用户根目录下建立文件夹“work”,用于保存所有 C 语言的程序,运行结果如下图所示:
- j' H' D6 R3 K; N- m
2 p' ^5 [2 x, e( r* D然后进入创建的文件夹 work,为了方便管理,我们每个例程都创建单独的文件夹,首先我们创建文件
8 U2 Z# P1 ]! W+ d5 v* O. |, f8 Q2 R夹“hello_world”来保存我们的第一个 C 程序,如下图所示:
, R8 g4 s3 X4 T, H, W! p3 t" T
" r3 p  f; n# U4 R然后进入上图中的 hello_world 文件夹,使用 vi 命令新建文件“main.c”,然后在里面输入下面的代
; g+ }8 w3 c) l4 K# x码:
: O" t2 N! v* U, N( j' S. N! t#include
4 C  ]& B* g" t& ~int mian(int argc, char *argv[])- D: Y% q- Y, z2 m
{' ]5 v9 x% U- j1 n
printf("Hello World!\n");
# c1 t+ Q; F& d$ E' dreturn 0;/ E: n. h, {4 ]9 e$ h% d3 z1 ]" o
}
4 l. f" Y' [- y2 Z/ i1 v编写完以后保存并退出 vim 编辑器,然后可以使用 cat 命令查看代码是否保存成功,如下图所示:: o# `1 M, Z& U
; C  @2 }& f% J; s, o+ k& l, v
通过上图可以看到代码已经编辑完成了。
( Y% ]7 l) C' C/ U  D; X3.1.2  代码编译
" h/ A& v" a" L) b' O2 wUbuntu 下使用 gcc 编译器来编译 C 程序,我们在安装 Ubuntu 系统的时候,gcc 编译器 morning 安装好
% q( @- M2 h! J3 Y1 S8 J! N% R# `了,我们可以在终端输入“gcc -v”来查看下 gcc 的版本,如下图所示:
. ~& O/ N: J1 ^ ' P0 `) Y& d( u. @" o) N
通过上图可以看到 gcc 的版本信息,说明我们的 Ubuntu 上已经安装了 gcc 编译器了,下面我们来看看. |5 A2 ~- @2 Y0 j. r: q
怎么通过 gcc 编译我们的第一个 C 程序,我们在终端输入“gcc main.c -o main”,然后回车,会生成 main 文
# K) o" s8 H$ l" y0 M件,如下图所示:! v+ [1 @  n3 f- k; s

- l5 e/ e9 N0 m* ^, x$ O在上面的“gcc main.c -o main”这个命令里面“-o“用来指定编译生成的文件名字,我们指定的是 main,5 z" n' N2 u! J& x6 X. a6 z9 H
所以在图 3.1.2.2 中可以看到生成了文件“main”,如果我们想生成其它的文件名字,我们只需要修改“-o”
, y! G+ U$ [! p6 D8 e8 O/ s后面的 main(修改成您希望生成的文件名字)。经过前面的步骤已经生成了可执行文件 main,现在我们演
& o  ^7 s+ V1 b9 X示下在终端如何运行一个可行性的程序,我们直接在终端当前目录下(可执行文件所在的目录下)输入+ u' L  f5 I( t, y
“./main”,然后回车,就可以运行可执行文件 main 了,运行结果如下图所示:
* X' q( ]! g6 L$ g7 R # Z8 k8 Z$ `$ ^* p. E
在上图中运行的命令“./main”,其中的“./”代表当前目录下。我们可以看到在终端打印出了 Hello
" G2 u2 ?4 c3 o' j* |World!。至此,Linux 下的 C 语言编辑和编译的一整套流程我们就介绍完了。
; ~4 p% [4 e0 {. I9 [* b3.2 c gcc 编译器 编译器& _4 ]; \% Y/ t8 R1 O
3.2.1 gcc  命令分析
( _. Y7 ]) _5 P+ D8 `1 h9 b在 3.1 节我们已经使用 gcc 编译了 Linux 下的第一个 C 程序,gcc 命令的格式如下:
* S0 ~0 n% W/ e8 Q6 g( ngcc [参数] [文件名]
' a" h' ^( r# @/ W3 n( Q0 n主要参数说明如下:. Z# ^- E/ m  |
-c 编译、汇编到目标代码(.o),不链接成可执行文件
8 z' h- r% d  e# g-g 生成调试信息2 d6 @% e+ i2 e; n9 f: ]: c* u* l9 {. b
-o 编译完成后生成的文件名,如果不使用该选项,默认生成 a.out 文件1 u2 k8 U8 U6 d# m7 i$ S
-O 对程序进行优化编译,产生的可执行文件执行效率高
; \8 r( `' q+ r& h- ~-w 不生成任何警告: L, N" m( s! D9 g# R, e
-S 仅编译到汇编语言,不进行汇编和链接- V0 A' k& g6 S! m* U
3.2.2  编译警告错误处理7 u. t7 @" d+ o
我们是 Windows 下使用 Keil 或者其他开发工具,在编译的时候,如果程序有错误,开发工具会提示出8 k1 P* R: P) n" e
具体的错误信息,可以很方便的定位到问题点,快速的修改出现的问题,gcc 同样也有类似的功能,下面我+ r0 S$ |. Y/ ~) S/ C
们来看下 gcc 的错误提示功能,首先我们在 work 目录下建立文件夹“test2”,然后使用 vim 在 test2 文件
2 U5 B# |' z/ ]0 x夹创建 main.c 文件夹,在 main.c 文件输入如下代码:
0 m( v0 |. |; r5 A! ^#include
& Q/ [( l7 l, B! P5 j( Q  X% \int main(int argc, char *argv[])8 I3 `( }- }2 L/ b" }  U+ r
{' g+ b8 T" N( P2 ^
int a;
! C& r1 J3 X/ Y' n: l! M, F9 Ta = 1+ r5 A6 Y" E) |3 N
printf("a=\n", a);4 b; x7 c# z! k' n0 P6 O
return 0;
: Z! i: t& S5 ?0 T* _}
" C. v+ m5 N; l% f# b( U0 }4 j$ N! z上面代码有两处错误:, H1 T3 M- d2 R: M' u: ?
第 7 行 最后缺少“;”, q0 U7 d$ }- n. I9 L8 c. _: \" q
第 9 行 printf 语法不对,应该为:printf("a =%d\n", a);# G: y* W+ D: N' v
我们使用 gcc 编译 main.c,可以看到 gcc 会提示错误信息,如下图所示:0 K5 \$ [( s( f: {% W: c

& y9 B1 a, q9 w1 \+ d从上图中可以看到在 mian.c 文件的第 9 行 printf 前面缺少“;”我们在第 7 行“a = 1”后面加上“;”,
% ~/ a$ Z1 o- u) c- G# o3 \然后继续编译,如下图所示:: Y- T6 R# q$ x3 H8 _

# V! f2 j, ~5 ~0 }从上图可以看出编译提示语法格式不对,我们把第 9 行修改成“printf("a=%d\n", a);”,然后在继续编1 f4 J& U9 W5 j3 G- ^0 d2 p' N
译,如下图所示:& d4 L+ ~  S, C; V* j5 D
% f1 b9 [) B  m
我们可以看到这次 gcc 编译通过,最终生成了文件 main。我们在终端执行 main,运行结果如下图所示:3 y# V  h( V  v5 O) w

( k, m2 s* {7 v* U/ U从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出) A7 n- {6 O7 i0 |4 U, r4 E# |+ W- Y
程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。0 v- E+ O( i1 @  P! Y
从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出% J9 q) ~# U2 R3 j8 h& g" ~
程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。
# E  e: h1 Z: m; l+ K3.2.3 gcc  编译流程
+ c, x2 o, b: l3 f5 {gcc 的编译流程可以分成四个步骤:9 }) B, o: U' d0 |+ Y" ], |
1.预处理,生成预编译文件(.文件)
" r% Z; g, u6 x4 x# Q2.编译,生成汇编代码(.S 文件)
( c( y% x- p' N* k% p- ]1 I2 @* i, @3.汇编,生成目标文件(.o 文件). H. L0 ]  v+ ~' G
4.链接,生成可执行文件
, _: m; x8 l$ I" a0 v8 ]3 3.3  初识  Makefile7 C9 _$ @- M  X- B
3.3.1  什么是 Makefile
( U' T  ]" k# ^3 \9 q0 x! b在 3.2 章节我们了解了在 Ubuntu 系统下通过 gcc 编译器来编译 C 程序,在我们演示的历程中只有一个+ d  y" C. Y6 X% d# @( V- a
C 文件,我们直接在终端输入 gcc 的编译命令,就完成了 C 程序的编译。我们在实际开发过程中,如果我们
. P0 \1 L5 m( V( R0 M4 D的工程有几十个,或者几百几千个 C 文件,我们通过在终端输入 gcc 命令来编译,这显然是不现实的。为
+ H0 W9 r" `8 @了解决这个问题我们可以使用“make”命令,它会解析 Makefile 文件中的指令(应该说是规则)来编译整* G! x% ?8 \9 v' T
个工程。在 Makefile 文件中描述了整个工程的所有文件的编译顺序,编译规则。
2 G' O& e7 o( p: h* k作为一个专业的程序员,一定要掌握 Makefile 的,我们可以通过 Makefile 能了解到整个工程的处理过程2 p* y' |9 f, b. O3 ^* c6 q
的。% o& z, i  l( N  X, ]/ c
由于Makefile涉及到很多的知识点,以至于可以单独写本书来讲述,所以本章我们只是讲解下Makefile
. ?" E; r. Z2 M的基础入门,如果详细的研究 Makefile,可以给大家推荐《跟我一起写 Makefile》这个电子文档,该文档
" y) v# \" m8 N9 ]# n! g: h已经放在了:i.MX6UL 终结者光盘资料\09_其它参考资料里面了。
, \5 R4 `" b) `) W3.3.2  第一个 Makefile# F: |& A4 F4 i' m
在本节我们建立这样一个工程,计算两个整形数的和,并将结果在终端显示出来。在这个工程中一共有6 c: Y. J! |7 F6 R( y
main.c、calc.c 两个 C 文件和 calc.h 这个头文件。其中 main.c 是主文件,calc.c 负责接收 main.c 传过6 g! e$ ?) U* o% @
来的数据,然后进行相加。main.c 文件的内用如下:7 F& s  {7 F6 u* V- V, ?
#include ) V% p8 [! d$ r) z* D) n+ z
include "calc.h"* c" V, X( L: x; C! i
int main(int argc, char *argv[])
$ O/ I7 N2 y/ X) Z% ^{: q& y9 X7 x. p
int a = 3, b = 6, sum;+ w# [+ t4 z$ s/ H" F$ F! O5 {
sum = calc(a, b);2 e, p3 D. Q; I4 \2 o( F- d; i& Y: }
printf("%d + %d = %d\n", a, b, sum);
$ [+ _/ [( `2 A, Z5 E' Rreturn 0;
# ]  D# q; @. K3 H$ X}+ `4 @: \& J( f
calc.c 文件内容如下:
4 }" q/ p- c3 E#include / Z9 t0 J9 w; h3 A
int calc(int a, ing b)
( y' i3 q4 |% X# B) G4 f{: S4 C7 i( g8 F2 e4 u4 D
return (a+b);
7 K: Z, K& @2 D}
7 z0 [( h, t1 q. o, E文件 calc.h 内容如下:% z) O. S: U' y8 p+ h. x# h% y
#ifndef _CALC_H
+ g& q4 R9 [( y0 ~9 |#define _CALC_H2 C$ Y) M" m, V# U, d  }4 o9 Y
int calc(int a, int b);# ~+ i6 p1 d* J/ F& o2 a& r/ S" D
#endif
* a* _, ?# E& _3 [3 ^1 ]上面就是我们这个工程的所有源文件,我们在终端使用 gcc 编译这个工程,在终端输入“gcc main.c
" y4 y2 }7 G% o, ~  b1 ucalc.c -o main”,该命令的意思是用 gcc 对 main.c、calc.c 进行编译,然后输出可执行文件 main,运行
1 f3 N& ?9 a! [- l5 k! C! q/ _结果如下图所示:! S3 @  d8 B0 c( B+ x
# j- {$ Q* ^# h) [  y. x6 q% @
通过上图可以看到生成了可执行文件 main,我们在终端运行 main 执行文件,运行结果如下图所示:) v3 c: R+ o, n7 j/ j

+ _& k% p' w1 V; x' q/ I% q我们可以看到上图的运行结果和我们设计的结果是一致的。由于我们的这个工程是有三个文件,如果
5 f- m  l% {' F1 E; N4 \6 r工程有几百个,几千个的文件,或者如果有一个文件被修改,使用上面的命令将会编译所有的文件,如果
6 C6 Q3 w2 W" S! \( L我们的工程有上万个文件,编译完一次工程所需要的时间就很可怕。最优的方法就是编译过一次以后,如
: \9 L* A( j2 r果后面在编译,只编译修改的文件,这样就会节约很多时间,因此我们修改下编译方法,命令如下:1 i$ X6 |1 o1 p. Q' v7 B
gcc -c main.c/ i8 j( M' H& C6 Q9 i
gcc -c calc.c
0 C. q7 ]" |$ q! l  V4 A+ n0 y9 S! {2 S5 lgcc main.o calc.o -o main
4 {) a: `7 M! L* Y* a+ u; c/ L我们在终端输入上面的命令,结果如下图所示:% k6 H8 A0 h1 w$ T

7 \( n0 {8 G+ ]6 c8 L7 V3 u$ N上图的第一条和第二条命令里面使用了参数“-c”是把 main.c 和 calc.c 编译成对应的.o 文件,最后
4 Q- @7 @" |( N5 x" o+ k7 }; G一条命令是把编译生成的.o 文件链接成可执行文件 main。假如我们修改了 main.c 这个文件。只需将 main.c/ H" [0 E. _4 |, ^
这个一个文件重新编译下,然后在把所有的.o 文件重新链接成可执行文件,对应的命令如下:
1 ^- Z4 x5 G# Q! e* M3 B+ z' u( bgcc -c main.c
) ]% o! P( o+ D6 r8 O0 x& \$ bgcc main.o calc.o -o main4 D1 Q0 H: s9 N% t. _
可是这样还有一个问题,如果需要修改的文件有很多,这样工作量也会不小,所以我们需要一个工具:
3 ~6 {: X8 L& d1.如果工程没有编译过,就会把工程中的.c 文件全部编译并连接成可执行文件
6 U  O6 N% Y) I# h1 v- u2.如果工程中有某些文件修改了,只编译修改的文件并连接成可执行文件" D! G6 M% L5 U4 X+ t, p
3.如果工程中的头文件修改了,那么就要编译所有引用这个头文件的.c 文件,并且连 接成可执: c- K$ E. K/ u" @+ k" K* I( L
行文件
, E  c! t) k6 y6 \, d我们开头说的 Makefile 就是完成这个功能的,下面我们在工程中建立一个 Makefile 文件来实现这样的功; j7 b# ^# G, F
能(注意:文件名字必须为 Makefile,大小写是区分的)。我们使用 vim 创建 Makefile 文件(Makefile
1 `0 J2 G$ @5 z7 b% ?和我们的 main.c、calc.c 在同一级目录下),然后输入下面的脚本:
4 `3 Q: i) g% V, R/ Fmain:main.o calc.o
5 H# l8 ~) R, [, Tgcc -o main main.o calc.o( M+ T* z1 I* t5 K6 y1 N
main.o:main.c, {1 R1 s0 y$ K9 K+ x
gcc -c main.c) {6 L5 R% h4 D+ `8 w
calc.o:calc.c
- J1 l6 p+ [& D- z2 sgcc -c calc.c5 G( @3 t: @9 j, h# g: l: J& N; f9 d; U
clean:
8 i8 L. _% @* R& i+ `rm -RF *.o4 w6 h; B2 z5 k6 f% U4 z2 U
rm -rf main2 \9 e3 y; q1 d9 b$ {$ A+ \
上面脚本缩进的行需要使用“Tab”键缩进,不要使用空格,这是 Makefile 的语法要求,编写完成的脚本
, e, s# I; G) L- U. Z: H7 I如下图所示:
0 Y" c+ I/ h4 D1 m1 @5 ]
' q# W4 ^, L' X1 D# v* t* t编写好 Makefile,保存并退出,然后我们在终端输入“make”命令来编译我们的工程,make 命令会在8 X+ \& Z9 v. d  U9 j' s% h0 j1 U2 s( }
当前目录下查找“Makefile”文件,如果存在的话就按照 Makefile 里面的规则进行编译,如下图所示:& ~2 Z2 l' y! T' [+ ~' C' g# n
' b  Q1 a2 J" Q" p+ \
通过上图可以看到编译产生了 main.o、calc.o 和 main 执行文件,说明编译成功了。接下来我们修改0 o* ]) F. a$ y% y
下 main.c 这个文件,如下图所示:; L( I( t6 e% W( p! N9 l0 c" D

. u' L* _: W$ C8 H  g然后保存并退出,然后在终端输入“make”再次编译下工程,如下图所示:  F+ Y% z: U4 M0 G4 C7 ~7 X
9 ?2 a7 B' O! m: Z9 x9 H
通过上图我们可以看到只重新编译了修改的 main.c,并最终重新链接生成可执行文件 main,我们在终, J6 x" D6 c! h. [: `2 F6 }9 S7 T
端运行可执行文件 main,如下图所示:: c1 b, S8 c9 [/ N$ Z
6 Q! d5 A9 x* i" ?
从上图的运行结果可以看到最后的结果等于 10 了,和我们程序的设计结果是一样的。( t5 ^* t- s+ j" l. o, S- q" h
3.4 e Makefile 语法 语法
% U( t0 x  E+ S" w5 t' z2 c3.4.1  初识 Makefile
* u7 D# v. w1 K5 i( UMakefile 文件是由一些列的规则组合而成的,格式如下:
1 p- v( g: ^9 C  {# Jtarget(目标文件) ...: prerequisites(依赖的文件) ...
2 S7 P+ u, ]( H, w6 Ocommand(命令)4 [, a# n. Q; ^2 d! t% `
...1 n( F& `# p+ f. U
...* z+ d. s. ?/ K3 u3 C6 _
比如 3.3.2 中写的 Makefile 的规则:% }, P1 J7 O& O4 u# W
main.o:main.c% y0 u  M9 |5 r; U8 P' h$ o
gcc -c main.c* ?  V0 r0 I% `
这条规则的 main.o 是目标文件(将要生成的文件),main.c 是依赖的文件(生成 main.o 需要的文件),, F. Z9 v) W6 y6 }* e2 S$ W
“gcc -c main.c”是生成 main.o 需要运行的命令。e Makefile  中每行的脚本如果有缩进的情况,必须使用+ `% A6 f  e9 B; t$ o! U
“ Tab ” 键缩进,切记不能使用空格缩进(这是 e Makefile  的语法要求),大家一定要切记!
) \( [  L( K$ v( J' G% f' F下面我们来分析一下图 3.3.2 章节中写的 Makefile 文件,脚本如下:
* n+ p, E- B/ s' V4 }$ l& `1 1 main:main.o calc.o7 @3 s# \3 T: c/ m* i
2 2 gcc -o main main.o calc.o; w  c6 u9 |+ C0 b
3 3 main.o:main.c
( ~6 E8 ~. E" n4 4 gcc -c main.c
( D$ Y8 C1 |. C0 G: s5 5 calc.o:calc.c
! a2 i! `( N, A+ |8 [; Z$ j+ i5 A$ Y6 6 gcc -c calc.c) F) [$ j0 a$ ~; {: c
7 7
* t* _4 C: N$ [! M" C, _8 8 clean:
: _) \# A4 B  v& L0 W9 9 rm -rf *.o5 A# O$ H$ D6 c3 h  q. b) |, b
10 rm -rf main8 _6 J. S' ^( J
该脚本一共有 4 条规则,1、2 行是第一条规则,3、4 行是第二条规则,5、6 是第三条规则 8、9、10
+ n$ _3 I! e) Y0 h. L是第四条规则。我们在运行 make 命令的时候,会解析当前目录下的这个 Makefile 文件里面的规则,首先7 Z8 i" N5 L  C& X- |6 J4 S
解析第一条规则,第一条规则中的目标文件是 main,只要完成了该目标文件的更新,整个 Makefile 的功能
4 D+ [/ R% _, H/ w! K; F就完成了。在第一次编译的时候,由于目标文件 main 不存在,则会解析第一条规则,第一条规则依赖文件$ t% \& C: e- [' x" `* c0 f/ Q
main.o、calc.o,make 命令会检查当前目录下是否有这两个.o 文件,经过检查发现没有,然后 make 会在
  W# F# h, F# `Makefile 中查找分别以 main.o、calc.o 为目标的规则(第二条,第三条规则)。执行第二条规则依赖的文
# P/ D1 G' _- k# l! d/ l件是 main.c,make 命令检查发现当前目录下有这个文件,然后执行第二条规则的命令“gcc -c main.c”
; P! Z& D' _, ?! t1 ^/ Y生成 main.o 文件。然后执行第三条规则,第三条规则的目标文件是 calc.o,依赖的文件是 calc.c,make# H( P7 `6 e  H- F! r. s
命令检查发现当前目录下存在该文件,然后执行第三条规则的命令“gcc -c calc.c”生成 calc.o 文件,- e* J2 p3 E7 T8 W( }
至此第一条规则依赖的 main.o、calc.o;两个文件已经生成了,然后运行第一条规则的命令“gcc -o main
5 }2 B5 A1 `; r/ Amain.o calc.o”生成 main 文件。因为 make 命令运行的时候会从 Makefile 的第一条规则开始解析,然后& d, N/ h. r! X( I% Y  L# f: r
根据第一条规则的依赖文件去遍历文件中的“对应规则”,然后在根据“对应规则”的依赖文件去遍历“对
( _" _- C# y) A, Y应的规则”,采用这样递归的方式会遍历出完成第一条规则所需要的所有规则。下面我们来看看第四条规
: f0 Z* N$ D2 c5 \9 C则的目标文件是 clean,我们通过查看发现该规则与第一条规则没有关联,所以我们在运行 make 命令的时+ _2 N2 Y7 @1 J
候,不会遍历到该规则。我们可以在终端输入“make clean”命令来运行第四条规则,第四条规则没有依9 e1 t) ~$ r) d5 A8 [
赖的文件,所以执行运行命令“rm -rf *.o”和“rm -rf main”,这两条命令的功能是删除以.o 问结尾的' {3 s& l* Y" h9 `4 r- H5 d
所有文件,删除文件 main,运行如下图所示:' U4 [1 H) ~% [/ j; `/ ^  p
, `  k/ w  Q2 e! m' S
通过上图可以看到 main.o、mcalc.o 和 main 三个文件已经删除了。通过该规则我们可以清除编译产生) g% h' {  L7 i7 V" |- U
的文件,实现工程的清理。
* |1 v7 h& i8 \我们再来总结一下 make 命令的执行过程:
2 _& z- `5 q5 \6 u( @! r1.make 命令会在当前目录下查找以 Makefile 命名的文件4 a) R- F1 i2 J8 T3 ]3 J* Y, Y
2.找到 Makefile 文件,就会按照 Makefile 里面的规则去编译,并生成最终文件: o9 _2 `% Y! u0 J- f
3.当发现目标文件不存在或者所依赖的文件比目标文件新(修改时间),就会执行规则对应的命令来更新。
8 M. e# y0 m' H) \+ {! ?% J7 ]我们可以看到 make 是一个工具,他会通过 Makefile 文件里面的内容来执行具体的编译过程。
" i+ ^  t9 o* V! G; ]3.4.2 Makefile  的变量% @! f/ H( O6 Q+ f' B  X- S
在 3.3.2 章节中的 Makefile 第一条规则:/ y" E. C% ]2 P( S7 @
main:main.o calc.o
0 A5 x( O4 {$ p* Ogcc -o main main.o calc.o4 d: G# _/ o! p$ S- m. \) |- ^" a9 Z
在该规则中 main.o、calc.o 这两个文件我们输入了两次,由于我们的额 Makefile 文件内容比较少,
: _) D! F5 ~/ G' r$ o7 \9 Y! i1 a如果 Makefile 复杂的情况下,这种重复的输入就会非常占用时间,而且修改起来也会很麻烦,为了解决这  A$ y0 B( w5 Q: x# r, L! r
个问题,Makefile 可以使用变量。Makefile 的变量是一个字符串。比如上面的规则我们声明一个变量,叫4 S9 W, m8 [$ I' r( p+ O: m
objects,objs 或者是 OBJ,反正不管是什么,只要能够表示 main.o、calc.o 就行了,我们修改上面的规0 a! b5 s+ N+ v$ ], f
: l( z$ ~0 C& D2 r# \4 R6 R
1 1 objects = main.o calc.o0 N& C# v$ g2 h7 ?" `
2 2 main( objects); O. S  a5 i" s  }# F- Q
3 3 gcc -o main $( objects)& C  [; Z# i) P4 x/ J
我们来分析下修改后的规则,首先第一行是我们定义了一个变量 objects,并给赋值“main.o calc.o”,/ P$ c4 t" [! q3 g* n
第二行、第三行用到了变量 objects。Makefile 中的变量引用方式是“$(变量名)”,变量 objects 的赋值! c, a& ]3 Y6 y
使用“=”,Makefile 中变量的赋值还可以使用“:=”、“?=”、“+=”,这四种赋值的区别如下:# ~; U2 e5 C$ }' U$ n
1. “= = ” 赋值符- v# ]) i  q5 E* |3 u
我们先在用户根目录的 work 目录下创建一个 Makefile 脚本,输入下面的内容:: o' m( f1 W) O4 {. T$ F5 q7 Z! T
1 ceshi1 = test  g( u1 R9 m& o
2 ceshi2 = $(ceshi1)
, ?7 M' n6 K$ V* c. S9 x' v" X4 @- Y3 ceshi1 = temp& Y/ b, s7 O2 V, X8 X: |: A
4& F3 O7 E+ h+ w; F4 |! R% e% r
5 out:
5 f& t; W' m4 {6 @Echo ceshi2(ceshi2)
6 D( f& C4 W1 T, f5 ~第一行我们定义了变量并赋值“test”,第二行定义了变量 ceshi2 并赋值变量 ceshi1,第三行修改变量
. C# q3 v$ t/ E& A- Xceshi1 的值为“temp”,第五行、第六行是输出变量 ceshi2 的值。我们在终端输入“make out”命令,如5 O  Z9 O0 N. r: N
下图所示:
5 }  \  {8 @! t% I   ?/ H/ d+ [1 ?; |$ o  q& W7 e9 \
在上图可以看到变量 ceshi2 的值是 temp,也就是变量 ceshi1 最后一次的赋值。
0 \' d1 d9 m; K7 C5 z$ C7 N" B; b2. “ := ” 赋值符6 f6 d# S: T2 V' f6 b" h0 t
我们修改“=”赋值符中的代码,第二行的“=”改成“:=”,代码如下:: |* C5 j9 ?3 y
1 ceshi1 = test
6 Q3 B$ d7 V3 t! N2 ceshi2 := $(ceshi1). h: F+ |5 m8 U# u$ x! ~4 a/ m' ^6 Y: M
3 ceshi1 = temp- Q: c' ]5 ?2 @
4
, ?" M' O" M+ t9 Q* M5 out:
! h7 e. S' _& n* g1 B( l/ \+ Z6 @echo ceshi2(ceshi2)4 J0 ?; l0 M/ P* e) F
我们在终端输入“make out”命令,如下图所示:
; K; K% t4 B, ~. l0 y+ H
" ~" w3 a  E2 h我们可以看到上图的运行结果输出变量 ceshi2 的值是 test,虽然在第三行我们修改了变量 ceshi1 的
9 u9 O; X: H* G. p; H+ E% S值,通过本实验我们可以看到“:=”赋值符的功能了。& B6 M4 ?* T* P
3. “ ?= ” 赋值符* ?4 _3 A2 Z/ ~
ceshi ?= test. p  b  ]* Z3 N
“?=”赋值符的作用是如果前面没有给变量 ceshi 赋值,那么变量就赋值“test”,如果前面已经赋值了,
6 S( Y7 l- P8 T# p就使用前面的赋值。) X# [; k" j" r5 c, F0 p
4. “ += ” 赋值符
. y# i; {7 ^0 W$ M6 }% ?objs = main.o; `' e5 S( q; L. F
objs += calc.o
2 b  s  J! W  ^+ V5 A' u6 `" v7 [/ ~上面的脚本最后变量 objs 的值是“main.o calc.o”,“+=”赋值符的功能是实现变量的追加。
& F. `' R+ D+ V! V: J) N' g* C! y# }  Z3.4.3  条件判断- x# {& `( ~% ~6 T' {% h% X; P
使用条件判断,可以让 make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量
1 }1 C& d+ l* [& b6 i2 `1 h的值,或是比较变量和常量的值。其语法有下面两种:4 U1 y+ X4 g7 g* a2 D; L2 F5 i2 d
1.
/ P$ x3 X! m' Q- Q<条件比较>. Z6 N! i' I7 D7 W8 x$ l% [
[条件为真时执行的脚本]: L+ u* K1 t6 A. F7 ^" N0 ~' h
endif
% D, @1 Z% o; I- E* o2.% y3 S8 M9 u( y% |' S# P
<条件比较>* E4 z! t2 x, m  o4 R5 t
[条件为真时执行的脚本]
* x" [& b; b' Y: _5 U$ U2 q0 Helse( q# K8 ]' l& N
[条件为假时执行的脚本]# W8 |1 ]) x: O' m+ L  i
endif
% G, b+ j1 d9 x9 V条件比较用到的比较关键字有:ifeq、ifneq、ifdef、ifndef。" g4 o( r+ L! e: m+ F/ O
ifeq 表示如果比较相等,语法如下:
# j0 |2 A3 m& g8 H- E2 I, ]ifeq(<参数 1>, <参数 2>): [1 L. |/ K6 Z0 J. [
ifneq 表示如果不相等,语法如下:
6 W9 I$ t- k# C/ a! `# oifneq(<参数 1>, <参数 2>)
; p  \8 u+ V& `2 O/ zifdef 表示如果定义了变量,语法如下:6 s  |# O: A, F) ~7 }/ x$ }; y
ifdef <变量名>* B  U7 ]+ n  u
ifndef 表示如果没有定义变量,语法如下:
7 i, \9 e2 i, `$ }* F) U  qifndef <变量名>1 J" Q- O6 O5 }0 A4 m. w
3.4.4  使用函数
$ Y7 @8 n9 z2 W) b2 y在 Makefile 中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make 所/ T% m* J' b( I3 _* T" _* Q: C
支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。' e1 `4 r3 k$ l3 C& Q% r
函数的调用很像变量的使用,也是以“$”来标识的,语法如下:
  g; A4 y5 C$ `8 [" ~4 q$(<函数名> <参数集合>)
: z9 }( ^) ]% Q" T5 {3 r或者:. e  B. Z% G' D. I) B, c" D8 x
${<函数名> <参数集合>}- i: ~" s: q! w( b8 j/ O! T
函数名和参数集合之间以空格分隔,参数集合的参数通过逗号分隔。函数调用以“$”开头,以圆括号或花+ G$ X  s: H. s- k5 {
括号把函数名和参数括起。感觉很像一个变量。函数中的参数可以使用变量。为了风格的统一,函数和变
  r2 \/ N3 k3 N7 Q量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。9 O: `" }8 A8 C/ W3 T
因为统一会更清楚,也会减少一些不必要的麻烦。& P' D7 H" K) l8 [+ C
接下来我们介绍几个常用的函数,其它的函数可以参考文档《跟我一起写 Makefile》。) y9 G8 P. R5 y4 E& W6 L
t 1.subst  函数
* }; G3 a2 l7 }, N0 y/ k" m$(subst ,,)
8 E& T5 ?- O  C5 H, Q5 S2 g# t此函数的功能是把字串中的字符串替换成,函数返回被替换过后的字符串。如下示例:
% `" t) Y- U1 Y# s5 Z5 x! }9 U: S$(subst ee,EE,feet on the street)4 ?  J. A$ q8 @! W0 |3 Y
以上脚本实现把字符串“feet on the street”中的“ee”字符串替换成“EE”字符串,替换后的字符串
- }' T# ]( j) s为“feet on the strEEt”。* I4 B+ P* E5 s* p
. 2. t patsubst  函数+ ?3 w6 ]/ X; C' Y
$(patsubst ,,)
, H+ r, c4 |- g/ w/ C$ b4 f2 {此函数的功能是查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式( M# {/ l) }* @2 e% Z8 X5 A. N
,如果匹配的话,则以替换。这里可以包括通配符“%”,表示任意长# I8 R+ q, n4 v0 B0 V
度的字串。如果中也包含“%”,那么中的这个“%”将是中的那个/ A/ }( e: p- W
“%”所代表的字串。(可以用“\”来转义,以“\%” 来表示真实含义的“%”字符)。函数返回被替换% d/ s0 j7 r! Y
过后的字符串。如下示例:
: l# ~7 F/ [$ ^" v9 J/ b$(patsubst %.c,%.o,x.c bar.c)( C/ M2 I( n6 r) C
以上脚本实现把字串“x.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.o bar.o”
- c# B4 G/ c8 t' A! I1 r2 \+ Lp 3.strip  函数
* y' H/ N2 f% u7 H, c5 T% |$(strip )- _7 |2 V/ j" w6 O6 ~* O+ _
此函数的功能是去掉字串中开头和结尾的空字符,函数返回被去掉空格的字符串值。如下示例:: r1 y+ e$ Z9 C* a+ c9 F
$(strip a b c )( D  O6 R7 o& a2 r' J1 |
以上脚本实现把字串“a b c ”去掉开头和结尾的空格,结果是“a b c”。/ [1 [& t- L% @& u
. 4. g findstring  函数  B5 s1 w, t! o7 _
$(findstring ,)
# [5 o( _* K8 D% o) n. K此函数的功能是在字串中查找字串,如果找到,那么返回,否则返回空字符串,如下示7 A; Y4 J3 X5 t+ ?
例:
' U6 D% O" l7 x8 x/ i$(findstring a,a b c)& D8 d. i+ V/ O3 A/ b$ v) ]+ E6 w+ c
$(findstring a,b c)
/ B8 A( K+ o! A: ~以上脚本,第一个返回“a”字符串,第二个返回空字符串。
8 n, B5 ]' ]- ~  D, Kr 5.dir  函数
/ V1 O5 n3 Q; B* E# |4 S$(dir )
$ ~7 F4 x' j+ g" V  l此函数的功能是从文件名序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部
- m( v. W8 Y# e9 Z2 ]9 Q分。如果没有反斜杠,那么返回“./”。返回文件名序列的目录部分,如下示例:0 n; e6 l1 T) J9 O
$(dir src/foo.c hacks)% A/ ?7 n  T. x: d( B, q
以上脚本运行结果返回“src/”。
) J1 K' J1 @0 e4 D! ?2 s  Z) n' x. 6. r notdir  函数
, ?2 M8 g" e! K7 F" O$(notdir )
/ s( N5 z( D: K1 o( s3 b此函数的功能是从文件名序列中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后/ k5 p. y6 g. n6 Z* c" M
的部分,返回文件名序列的非目录部分,如下示例:& e$ B% Q6 n; i% P3 S( s
$(notdir src/foo.c)7 H7 Z! q0 e. f0 e- b3 O
以上脚本返回字符串“foo.c”
. m' Q6 t3 w, l5 E! E2 a( _; m. 7. h foreach  函数+ V4 N4 D2 L/ V7 s7 \# _
$(foreach ,,)
1 v( w" x6 o' D, A3 e7 R0 ?此函数的功能是把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含# a4 r6 D. S! ?( N
的表达式。每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,, P: k( K5 w/ a
最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函
9 r; M0 `! d% L' ]6 g9 L) l- s4 S数的返回值。所以,最好是一个变量名,可以是一个表达式,而中一般会使用这4 ]5 @1 y, Q* Q+ x3 \' H5 @) v6 A# c
个参数来依次枚举中的单词。如下示例:
' p9 g: @9 h. y% V9 Xnames := a b c d6 a' b; l' r. W: i  ]/ O3 ]; L
files := $(foreach n,$(names),$(n).o)
; T7 |6 _! `. C$ u. |以上脚本实现$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出" w! b: g9 [" q- b3 ~; f* E- w$ M+ k
一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以$(files)的值是“a.o b.o c.o d.o”。) N: t% i& t$ `6 m7 _# I
(注意,foreach 中的参数是一个临时的局部变量,foreach 函数执行完后,参数的变量将不" U- G2 ]) O; i3 n" q. ?& Y: `; o
在作用,其作用域只在 foreach 函数当中)。
1 [8 [# q8 ~7 W) v% D+ e更多内容关注迅为电子  G) Z' T+ E/ d4 E
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-10-26 10:15 , Processed in 0.203125 second(s), 27 queries , Gzip On.

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

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

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