ARM架构与编程5--gcc与Makefile(基于百问网ARM架构与编程教程视频)

一、gcc安装

使用keil开发,程序的编译、汇编、连接在keil软件中都被封装好了,用户只需要点击对应按钮就可以了,但实质还是keil内部调用了命令行对程序进行编译、汇编、连接。

两个常用的编译器:

1、armcc

  • ARM公司的编译器
  • keil使用的就是armcc

2、gcc

  • GNU工具链
  • Linux等开源软件经常使用gcc

安装的是windows下的gcc工具链,他与arm-linux-gcc是类似的。

这里使用的是codeblock的gcc工具,具体下载安装过程就不做介绍了,注意的是要安装带有GCC的版本。

在这里插入图片描述

下载完成后,找到codeblock的gcc.exe的路径D:\Code Block\CodeBlocks\MinGW\bin,最后设置系统的环境变量。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在Git中测试一下gcc工具链是否安装成功。

1、编写一段代码,进行测试。文件路径是E:\100ask\gcc_Template\hello_test\hello.c

#include <stdio.h>
int main(void)
{
	printf("hello, world!\n");
	return 0;
}

2、打开Git Bash,输入gcc -v

在这里插入图片描述

3、测试代码

进入编写好的代码的路径,

$ cd /e/100ask/gcc_Template/hello_test

编译程序

$ gcc -o hello hello.c

执行程序

$ ./hello.exe

查看输出

在这里插入图片描述

二、gcc编译过程

2.1 程序编译的4步

前面说过,程序从编辑到最后生成可执行代码,一共有四步。

1、预处理:主要工作是把头文件包含进来、把宏给替换、确定条件编译代码等。这个步骤不检查语法有没有错误。经过预处理后,由.c文件生成.i预处理文件。

2、编译:主要工作是根据预处理文件生成汇编文件。这里会检查语法错误,没错误才会生成汇编文件。.i文件生成.s汇编文件。

3、汇编:主要工作是把汇编文件变为可重定位目标文件。.s文件生成.o文件。

4、链接:主要工作是把所有用到的.o文件全部链接起来,生成一个可执行目标文件。多个.o文件生成.elf文件。

在这里插入图片描述

通常编译有多种解释,有时候指预处理、编译、汇编、链接所有步骤,有时候可能指的是预处理、编译、汇编,也可能代表只是编译一步,所以不同情况下的编译的步骤不是确定的。

2.2 使用gcc编译

2.2.1 gcc常用命令选项

选项功能
-v查看gcc编译器的版本,显示gcc执行时的详细过程
-o 指定输出文件名为file,这个名称不能跟源文件名同名
-E只预处理,不会编译、汇编、链接
-S只编译,不会汇编、链接
-c编译和汇编,不会链接
gcc -X -o name name_source

上面是gcc使用例子,X表示上面表格中的选项,name表示生成的新文件名字,name_source表示要操作的文件名字。

比如gcc -E -o led.i led.c这条命令表示根据对led.c文件进行预处理,生成led.i的预处理文件。-E表示只进行预处理,不进行编译、汇编、链接。

在这里插入图片描述

程序处理的过程如下:

hello.c(预处理)->hello.i(编译)->hello.s(汇编)->hello.o(链接)->hello

在这里插入图片描述

所有步骤如下:

gcc -E -o hello.i hello.c
gcc -S -o hello.s hello.i
gcc -c -o hello.o hello.s
gcc -o hello hello.o

这样生成可执行文件比较麻烦,gcc会对.c文件默认进行预处理操作,使用-c再来指明了编译、汇编,从而得到.o文件,再将.o文件进行链接,得到可执行应用程序。简化如下:

gcc -c -o hello.o hello.c
gcc -o hello hello.o

一般情况下,都使用这两条语句进行开发。对于多个C文件,每个都可以单独生成.o文件,最后在统一链接。

三、Makefile

3.1 gcc的弊端

对于多个文件的工程来说,比如一个工程包含a.c、b.c、c.c。

执行gcc -o test a.c b.c c.c的具体步骤如下:

1、对于a.c:预处理、编译、汇编生成a.o文件。

2、对于b.c:预处理、编译、汇编生成b.o文件。

3、对于c.c:预处理、编译、汇编生成c.o文件。

4、将a.o、b.o、c.o连接起来得到test应用程序。

通常把预处理、编译、汇编这三步统称为编译。

第一次是这样操作的,但是当只修改其中一个文件比如只修改了a.c文件,再次执行gcc -o test a.c b.c c.c的时候,除了重新编译a.c,对于没有修改的b.c和c.c也会重新编译。这样就会使得程序编译的速度大大降低,如果一个程序中有非常多个文件的话,就会导致非常多的效率问题,因为只是修改了一个文件,却要把所有的文件就会重新编译一次,编译的时候就会等待很长时间。

正确的做法应该是只重新编译修改过的文件,而没有修改过的文件不会重新编译。通常是按下面的方式处理:

不使用gcc -o test a.c b.c c.c直接生成应用程序,而是先把所有文件一个个编译下来,最后在统一链接,这样如果某个文件被修改了,只需要使用gcc -o x.o x.c命令将修改后的文件重新编译一次就可以了,然后重新链接所有的文件。

//编译
gcc -o a.o a.c
gcc -o b.o b.c
gcc -o c.o c.c
......
//链接
gcc -o test a.o b.o c.c ......

3.2 Makeflie的规则

根据上面的情况来看,若修改的文件量非常多,也是需要一个个的重新编译的,这样也是很费时间的,那么我们可不可以像C语言那样,封装个函数func_compile,制定一个规则,若x文件被修改了,调用gcc -o x.o x.c重新编译,若文件没有被修改,则无操作。

基于这种情况,我们引入了Makefile。

Makefile文件内部就制定了上面说的规则,一般情况下,使用make命令就行。make命令就相当于func_compile函数。根据Makefile文件中的规则,来对某些被修改过的文件重新编译。那么怎么知道哪些文件是被修改了呢------比较时间,如果a.o、b.o、c.o的时间比test的时间更新,就表示文件被修改过了,就会重新生成test文件。这个新是指文件的最后一次修改时间

makefie最基本的语法是规则,规则如下:

目标 : 依赖1 依赖2 ...
[TAB]命令

目标就是想要生成的可执行文件名,依赖就是用到的文件,比如main.c中,用到了a.c、b.c中的东西,那么a.c、b.c就是目标的依赖文件。[TAB]就是TAB按键,命令就是要操作的命令。当目标文件不存在或者依赖文件比目标文件新的时候,就会执行下面的命令

test :a.o b.o  //test是目标,依赖于a.o b.o文件,当a.o或b.o比test新的时候,就执行下面的命令,重新生成test可执行程序。
gcc -o test a.o b.o
a.o : a.c  //a.o依赖于a.c,当a.c更加新的话,执行下面的命令来生成a.o
gcc -c -o a.o a.c
b.o : b.c  //b.o依赖于b.c,当b.c更加新的话,执行下面的命令,来生成b.o
gcc -c -o b.o b.c

执行make命令的时候,就会在当前目录下面找到名字为:Makefile的文件,根据里面的内容来执行里面的判断/命令。

3.3 Git中无法使用make命令

一般出现这个-bash: make: command not found提示,是因为安装系统的时候使用的是最小化mini安装,系统没有安装make、vim等常用命令。

可在以下网址中查看如何解决。

https://blog.csdn.net/qq_38420206/article/details/120750697

3.4 Makefile语法

下面介绍一些简单的语法。

make [目标]

若无目标,则默认执行第一个目标。

3.4.1 通配符

在上面的Makefile文件中,对于一个文件要有一个规则段实现。

test: a.o b.o c.o d.o
	gcc -o test a.o b.o c.o d.o ......
a.o : a.c 
	gcc -c -o a.o a.c
b.o : b.c  
	gcc -c -o b.o b.c
c.o : c.c  
	gcc -c -o c.o c.c
d.o : d.c  
	gcc -c -o d.o d.c
......

当文件比较多的时候,如果还按照上面的写法,既费时又费力。我们可以使用通配符来替换各个文件的名字。

test: a.o b.o c.o d.o ....
	gcc -o test $^
%.o : %.c
	gcc -c -o $@ $<

%.o:表示所有的.o文件

%.c:表示所有的.c文件

$@:表示目标

$<:表示第1个依赖文件

$^:表示所有依赖文件

3.4.2 PHONY

我们之前说,一个规则能执行的条件:

1、目标文件不存在
2、依赖文件比目标新

如果目标的名字和文件的名字重复了,会发生什么。

clean:
	rm *.o test

在这里插入图片描述

在这里插入图片描述

执行make clean命令,他是对文件clean操作,而不是执行makefile中的命令。

所以当makefile中的目标名和外部文件名冲突的时候,不是执行makefile命令,这时候要用到.PHONY。在Makefile中,.PHONY后面的目标表示的也是一个伪造的目标, 而不是真实存在的文件,注意Makefile的target默认是文件。

.PHONY: clean
clean:
	rm *.o test

在这里插入图片描述

可以看到,这时make clean就是执行的makefile中的命令。

3.4.3 变量

在makefile中有两种变量:

1、简单变量(即时变量):变量的值会在定义的时候就确定。即使变量使用 “:=” 表示。例如A := 100。

2、延时变量:延时变量在定义的时候不确定具体值,是空的,只有在使用的时候才会被确定。用“=”表示。

想使用变量的时候使用“$”来引用,如果不想看到命令,可以在命令的前面加上"@"符号,就不会显示命令。

当我们执行make命令的时候,make这个指令本身,会把整个Makefile读进去,进行全部分析,然后解析里面的变量。常用的变量的定义如下:

:= # 简单赋值,编程语言中常规理解的赋值方式,只对当前语句的变量有效。
= # 递归赋值,语句可能影响多个变量,所有目标变量相关的其他变量都受影响。
?= # 条件赋值,如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。
+= # 追加赋值,原变量用空格隔开的方式追加一个新值。
3.4.3.1 简单赋值

echo 可以在make执行时打印出自己预设的内容。

x := old
y := $(x)_flag
x := new
test1:
	@echo "x=>$(x)"
	@echo "y=>$(y)"

结果:

x=>new
y=>old_flag

x的值在后边被改变了,但是之前依赖于x的y没有改变,仍然使用x之前的值。

3.4.3.1 递归赋值
x = old
y = $(x)_flag
x = new
test1:
	@echo "x=>$(x)"
	@echo "y=>$(y)"

结果:

x=>new
y=>new_flag

可以看到,y的值跟随x的变化而变化。

3.4.3.1 条件赋值
x = old
y = $(x)_flag
x ?= new
test1:
	@echo "x=>$(x)"
	@echo "y=>$(y)"

结果:

x=>old
y=>old_flag

可以看到,给x赋值为new并没有生效。

3.4.3.1 追加赋值
x := old
y := $(x)_flag
x += $(y)
test1:
	@echo "x=>$(x)"
	@echo "y=>$(y)"

结果:

x=>old old_flag
y=>old_flag

x和y之间有个空格。

3.4.3.5 实例
A := $(C)
B = $(C)
C = abc
D := ddd
D ?= makefile
all:
	@echo A = $(A)
	@echo B = $(B)
	@echo C = $(C)
	@echo D = $(D)
C += 123

运行结果:

A =
B = abc 123
C = abc 123
D = ddd

A定义的时候等于C,但是此时C还没被赋值,所以A等于空。

B用等于号赋值,所以他的值跟随C的变化而变化,C在最后用+=追加赋值,所以C=abc 123,所以B、C最后都是abc 123。

D使用条件赋值,在前面赋值ddd,所以后面重新赋值的语句不执行,所以D=ddd。

3.5 Makefile函数

下面介绍几个常用的函数,注意引用函数使用“$”。

3.5.1 foreach函数

$(foreach var,list,text)

简单理解foreach函数就是个批量的字符串处理函数。把参数list;中的单词逐一取出放到参数var所指定的变量中,然后再执行text所包含的表达式。每一次text会返回一个字符串,循环过程中text的所返回的每个字符串会以空格分隔,最后当整个循环结束时,text所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

注意,foreach中的var参数是一个临时的局部变量,foreach函数执行完后,参数var的变量将不在作用,其作用域只在foreach函数中。

A := a b c d
B = $(foreach f, $(A), $(f)123)
all:
	@echo B = $(B)

输出结果:

B = a123 b123 c123 d123

3.5.2 filter函数

$(filter pattern...,text)     # 在text中取出符合patten格式的值

过滤掉字串text中所有不符合模式pattern的单词,保留所有符合此模式的单词。可以使用多个模式,之间使用空格分割。

A = a.c b.i c.s c.o e.elf abcd 1234
B = $(filter %.c %.elf, $(A))
all:
	@echo "B => $(B)"

输出结果:

D => a.c e.elf abcd

3.5.3 filter-out函数

filter-out函数于filter函数相反,他过滤掉字串text中所有符合模式pattern的单词,保留所有不符合此模式的单词。可以使用多个模式,之间使用空格分割。

A = a.c b.i c.s c.o e.elf abcd 1234
B = $(filter-out %.c %.elf, $(A))
all:
	@echo "B => $(B)"

输出结果:

D => b.i c.s c.o 1234

3.5.4 wildcard函数

$(wildcard pattern) # pattern定义了文件名的格式, wildcard取出其中存在的文件。

在这里插入图片描述

在目录中有C文件和O文件。

A = $(wildcard *.c)
B = $(wildcard *.o)
all:
	@echo "A => $(A)"
	@echo "B => $(B)"

输出结果:

A => A.c B.c C.c
B => A.o B.o C.o

3.5.5 patsubst函数

$(patsubst pattern,replacement,\$(var))

patsubst 函数是从 var 变量里面取出每一个值,如果这个符合 pattern 格式,把它替换成 replacement 格式,

实例:

A = a.c b.c c.c d.c e.c abc
B = $(patsubst %.c, %.d, $(A))
all:
	@echo "B => $(B)"

输出结果:

B =>  a.d  b.d  c.d  d.d  e.d abc

对于不符合格式的变量不做处理。

3.6 makefile改进

3.6.1 自动生成依赖文件

test : A.o B.o C.o
	gcc -o test $^
%.o : %.c 
	gcc -c -o $@ $<

上述文件中没有用到.h文件,如果要在.h文件中修改或者添加定义,那么是不会影响到test的,因为他的依赖中没有对应的头文件。但是要一个个的把所有头文件都手动添加上去的话也不现实,文件少点还好,如果有几百几千个文件呢。所以我们可以让makefile自动生成头文件依赖。这样不论是什么文件被修改,test的被修改的依赖文件都会被重新编译。下面是几条生成依赖文件的指令。

gcc -M c.c // 打印出依赖
gcc -M -MF c.d c.c  // 把依赖写入文件c.d
gcc -c -o c.o c.c -MD -MF c.d  // 编译c.o, 把依赖写入文件c.d

打印依赖:

在这里插入图片描述

生成依赖文件:

在这里插入图片描述

生成依赖文件并编译:

在这里插入图片描述

修改后的makefile:

objs = a.o b.o c.o	#所有的包含文件
dep_files := $(patsubst %,.%.d, $(objs)) #把obj中所有文件添加前后缀,变成.a.o.d
dep_files := $(wildcard $(dep_files)) #取出所有.a.o.d文件
test: $(objs) #test依赖于所有的obj
	gcc -o test $^
ifneq ($(dep_files),)	#判断文件是否存在
include $(dep_files)	#文件存在,添加进来
endif
%.o : %.c
	gcc -c -o $@ $< -MD -MF .$@.d	#生成对应的依赖文件.x.o.d,然后编译生成.o文件
clean:
	rm *.o test	#清楚编译产生的.o文件
distclean:
	rm $(dep_files)	#清楚生成的依赖文件,.x.o.d
.PHONY: clean	

这样不论修改了哪个.h文件,最终都会影响最后生成的文件,不需要手动添加.h、.c、.o文件,完成了支持头文件依赖。

3.6.2 添加编译参数

下面再添加CFLAGS,即编译参数。比如加上编译参数-Werror,把所有的警告当成错误。

CFLAGS = -Werror -Iinclude
…………
%.o : %.c
	gcc $(CFLAGS) -c -o $@ $< -MD -MF .$@.d

用-Werror设置,所有的警告会变为错误,必须要解决这些错误编译才能进行。

除了编译参数-Werror,还可以加上-I参数,指定头文件路径,-Iinclude表示当前的inclue文件夹下。
此时就可以把c.c文件里的#include ".h"改为#include <c.h>,前者表示当前目录,后者表示编译器指定的路径和GCC路径。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值