一、工程管理文件Makefile
1.什么是makefile?
用来管理一个工程中的所有关联文件的文件:头文件、源文件、库文件。。。
2.makefile文件在工程中一定要有吗?
不一定
一般原则,若编译命令比较复杂时,会写makefile文件
若源文件较多时,也会写
目的是为了简化编译时的复杂度
二、项目工程中的文件组成
1.简单版本
源程序 main.c 包含主函数main
功能函数 func1.c
功能函数 func2.c
…
头文件 my_head.h 包含系统头文件,函数声明,宏定义,结构体声明。。。
库文件
makefile文件:
project:project.c fun1.c fun2.c
gcc $^ -o $@ -I .
-I include 向编译器表明指定的头文件的存放路径
-L lib 向编译器表明指定的库的存放路径
-l 向编译器表明所调用的库的名字
找到对应的库的文件,库的文件名的组成:lib+库的名字+.so/.a+版本号+修正号
例子:
库文件的名字:libapi_v4l2_arm.so.8.1
库的名字:api_v4l2_arm
指定该库的方式:-lapi_v4l2_arm
问题:
若执行make,通过makefile文件进行编译时,提示没有make
Command 'make' not found, but can be installed with:
sudo apt install make # version 4.2.1-1.2, or
sudo apt install make-guile # version 4.2.1-1.2
解决办法:
安装make
首先保证虚拟机能连接外网
验证方式是ping百度:ping www.baidu.com
执行make的下载安装命令:sudo apt-get install make
三、makefile的书写规则
1.makefile书写的两个基本规则:“依赖”+“目标”
依赖 指的是编译文件时,所需要的源文件
若书写makefile不是为了编译,则不需要写依赖
目标 指的是编译文件时,你最终想要得到的文件
2.makefile的书写规则
无依赖:
1.确定目标叫什么名字
2.按照以下规则写Makefile
目标:
<tab键>执行规则
例子:使用makefile输出helloworld
target:
@echo helloworld 在执行规则前加@,那么执行结果就不会出现执行规则
注意:<tab键>长度相当于四个空格,不等于四个空格,执行规则是能够识别的
有依赖:
1.确定目标叫什么名字
2.按照以下规则写Makefile
目标:依赖(若有多个依赖,则每一个依赖之间使用空格进行间隔)
<tab键>执行规则
例子:使用makefile编译一个简单的程序
hello:helloworld.c
gcc $^ -o $@
注意:如何在执行规则中调用目标和依赖
$^ 表示所有的依赖,等价于helloworld.c
$@ 表示目标,等价于hello
重复执行make进行编译,会怎么样?
会失败,因为目标提示已经是最新的了,makefile在进行编译之前,都会检测所有的依赖对应的修改时间
若修改时间与上次编译的修改时间一致,则不会进行编译
若任意一个依赖的时间被修改,则会进行新的编译
四、当makefile文件有多个目标时
例子:
target:
echo helloworld
target1:
echo 1234
target2:
echo abcd
执行make 没有执行目标时,默认执行第一个目标,等价于make target
若想执行其它目标,则需指定,例如执行第二个目标:make target1
五、makefile的变量种类
1.自定义变量
在makefile中定义自定义变量,不需要声明数据的类型,只需要定义名字即可
因为在makefile中,所有的变量都是默认为字符串类型
abc 默认为字符串类型的变量
规定:
1.变量名的命名规则和C语言一致
有大小字母、数字、下划线组成,首字母不能为数字
2.给变量赋值时,等号两边可以有空格,也可以没有空格
例子:
ABC = hello
ABC=hello
3.引用变量的值时,需要在变量的前面添加一个$,相当于解释引用
注意:makefile中引用变量时需要()
例子:
ABC = hello
DEF = $(ABC)world
4.相对于C的书写而言,不同的地方是,缺少""
因为makefile中的变量都是字符串类型,所以""可以省略
例子:
ABC = hello
ABC = “hello”
5.利用变量进行目标与依赖的描述
将目标与依赖保存在变量中,写规则时,引用变量,等价于引用目标与依赖
例子:
原本的:
project:project.c fun1.c fun2.c
gcc $^ -o $@ -I .
更改后:
TARGET = project
C_SOURCE = project.c fun1.c fun2.c
PATH_INCLUDE = -I .
(
T
A
R
G
E
T
)
:
(TARGET):
(TARGET):(C_SOURCE)
gcc $^ -o $@ $(PATH_INCLUDE)
2.系统预设定变量
CC 编译器的名字,默认等于cc,cc等价于gcc,既是CC=gcc
RM 删除命令,默认等价于rm -f(不可忽略文件),既是RM=rm -f
若这些变量不赋值,就按照默认的值来进行使用,但是这些量也可以被赋值
更改后:
TARGET = project
C_SOURCE = project.c fun1.c fun2.c
PATH_INCLUDE = -I .
(
T
A
R
G
E
T
)
:
(TARGET):
(TARGET):(C_SOURCE)
$(CC) $^ -o $@ $(PATH_INCLUDE)
clean:
$(RM) $(TARGET)
3.自动变化的量
$^ 代表所有的依赖
$@ 代表目标
六、makefile的伪指令
场景1:
假设makefile中一套规则
clean:
$(RM) project
当我们执行make clean时,makefile就会执行这台规则
场景2:
假设makefile中一套规则,并且当前目录下有一个叫做clean/的目录
clean:
$(RM) project
当我们执行make clean时,会告诉你
make: ‘clean’ is up to date.
如何告诉你的编译器,这个是一套规则,而不是一个生成文件?
解决办法:使用伪指令
.PHONY:clean
clean:
$(RM) project
七、makefile的函数
1.wildcard 在指定的路径下寻找匹配的文件
C语言调用函数:函数名(参数1,参数2,参数3…)
makefile调用函数:$(函数名 参数1,参数2,参数3…)
例子:
想要把当前目录下的所有的C文件都找出来,并进行变量保存
C_SOURCE = $(wildcard ./*.c)
2.patsubst 修改文件后缀,并将这些改完的名字放置到一个变量中
八、库文件的概念
1.什么是库文件?
把一些程序、文件进行的整合打包,起到一个保密的作用
/usr/lib 系统自带的库文件,有些是用户移植的
/usr/local/lib 用户自己移植的库文件
/usr/5.4.0_arm/5.4.0/usr/lib 交叉工具链的库文件所在地
2.库文件的存放数据
1.以为库文件本身是一个二进制文件,所以看不懂,所以能起到保密的作用
2.库文件中存放的数据,其实就是一些函数
可以把多个功能函数整合保存成一个库文件
功能程序 处理 一个可以代表这些功能函数的库文件
fun1.c fun2.c ---> libxxx.so
九、项目中,库文件的作用
1.可以保护代码的安全性
项目完成后 功能函数制作成库文件 卖给客户 执行
2.可以让代码编程变得很简单
只需要在函数中调用库文件的函数接口
十、如何制作库文件
1.库的格式分类
静态库 命令:libxxx.a
动态库 命令:libxxx.so
库的文件名的组成:lib+库的名字+.so/.a+版本号+修正号
2.静态库和动态库的区别
静态库:主函数调用静态库中的功能函数,类似于去图书馆中直接把某本书拿回来
优点:使用静态库编译完成之后,无论静态库文件是否还存在,都不会影响可执行程序的执行
意思是图书馆是否还有同样的书,不影响我当前的读书体验
缺点:编译生成的可执行文件比较大
意思是为了能随时看这本书,要把这本书随时带在身上,比较累赘
动态库:主函数调用动态库中的功能函数,类似于首先查看图书馆是否有对应书籍,然后在我想要看的时候再去拿回来
优点:编译生成的可执行文件比较小
意思是我知道图书馆有这本书,我可以先准备着
缺点:程序运行时,对应的动态库必须存在,能被程序访问到,才能正常执行下去
意思是当我想看这本书时,要保证能拿到这本书
3.制作静态库
1、需要的函数源码
main.c 负责调用功能
fun1.c
fun2.c 功能函数的实现过程
my_head.h 函数声明
2.将项目中除main函数之外的所有.c文件编译成.o文件
gcc fun1.c -o fun1.o -c
gcc fun2.c -o fun2.o -c
3.将所有的.o文件打包成一个.a文件
ar rcs libmy_ggy.a fun1.o fun2.o
ar 制作静态库的命令
r running
c configure
s setting
4.编译最后的可执行程序
因为使用静态库参与编译,所以编译会拷贝库中的东西
gcc project.c -o project -I . -lmy_ggy
5.无论库是否还在,都可以执行
./project
4.制作动态库
1、需要的函数源码
main.c 负责调用功能
fun1.c
fun2.c 功能函数的实现过程
my_head.h 函数声明
2.将项目中除main函数之外的所有.c文件编译成.o文件
gcc fun1.c -o fun1.o -c -fPIC
gcc fun2.c -o fun2.o -c -fPIC
3.将所有的.o文件打包成一个.so文件
gcc -fPIC -shared -o libggy.so fun1.o fun2.o
4.编译最后的可执行程序
gcc project.c -o project -I . -lggy
5.执行
./project
执行时,很可能报错,原因是程序执行找不到对应的库
解决办法:
1.把你的库文件加入到系统所指定的某个库文件的存放位置
2.添加库文件所在的位置为系统的库文件的环境设置
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/xxx/xxx(自己的库文件所在的位置)
二.gcc编译过程
Makefile的模板
#定义变量保存gcc
CC=gcc 编译器,看情况修改
ELF=target 最终生成的目标文件
CONFIG= -I./include 查找头文件
LIBS += -L./lib 查找库文件,链接库文件
SRCS= ( w i l d c a r d ∗ . c ) w i l d c a r d 用 于 查 找 所 有 的 c 文 件 S R C S = m a i n . c h e l l o . c O B J S = (wildcard *.c) wildcard 用于查找 所有的 c 文件 SRCS = main.c hello.c OBJS= (wildcard∗.c)wildcard用于查找所有的c文件SRCS=main.chello.cOBJS=(patsubst %.c, %.o, $(SRCS)) patsubst 替换功能 OBJS = main.o hello.o
(
E
L
F
)
:
(ELF):
(ELF):(OBJS)
$(CC) -o $@ $^ $(LIBS)
−
−
>
依
赖
,
^ -->依赖 ,
−−>依赖,@ —》目标
gcc -o target main.o hello.o -L./lib
%.o:%.c
$(CC) -c $< -o $@ $(CONFIG)
clean:
rm *.o $(ELF)
(2)gcc编译过程
预处理 —》编译 —》汇编 —》链接
预处理 —》头文件,宏替换
gcc -E hello.c -o hello.i
编译 —》将C换成汇编语言 Compile only; do not assemble or link.
gcc -S hello.i -o hello.s
gcc -S hello.c -o hello.s 这一步相当于上面两步
汇编 —》检查语法词法
gcc -c hello.s -o hello.o
gcc -c hello.c -o hello.o 这一步相当于上面三步
链接 —》链接空文件
gcc main.o hello.o -o target -L./xxx -lxxx 这一步我们有可能需要链接其它库文件
注意:gcc main.c -o main.o 或者gcc main.c -o main会出错,因为缺少所依赖的目标文件,但是
gcc -c main.c -o main.o 是可以的