一、工程管理文件makefile。
1、什么是makefile?
makefile叫做工程管理文件,用于管理一个工程中所有关联的文件,例如:头文件,库文件,源文件..
2、makefile在一个工程中是不是一定要写的?
不一定要写。一般原则:如果编译命令复杂,就会写makefile。
项目中文件比较多的时候,一般都会写makefile去管理所有的文件。
项目中文件比较少的时候,一般不会写makefile,因为编译命令比较简单。
makefile作用就是为了简化编译时的复杂度。
二、项目工程中应该由那些文件组成?
1、简单版。 -> 全部的文件都是放在相同的路径。
源程序文件 main.c -> 包含了main函数在内的.c文件。
功能程序文件 fun1.c -> 第1个功能的文件。
功能程序文件 fun2.c -> 第2个功能的文件。
.....
头文件 my_head.h -> 结构体声明,函数声明,宏定义,系统的头文件
库文件
工程管理文件 makefile
参考: easy/目录
2、复杂版。 -> 对应的文件放在对应的路径下。
diff/ -> main/ -> main.c -> 包含了main函数在内的.c文件。
src/ -> fun1.c -> 第1个功能的文件。
-> fun2.c -> 第2个功能的文件。
include/ -> my_head.h -> 头文件
lib/ -> 库文件
bin/ -> 可执行文件
makefile -> 工程管理文件
以简单版为例子:
如果不写makefile,那么编译命令: gcc main.c fun1.c fun2.c -o main -I .
如果写了makefile,只需要输入:make
三、makefile书写规则?
1、了解makefile书写规则中的两个核心: "依赖" 和 "目标"
"依赖" -> 指的是编译文件时,才需要依赖,一般指的是.c文件。
-> 如果写makefile,不是为了编译,则不需要写依赖。
"目标" -> 指的是编译文件时,你最终想得到的文件。
main.c fun1.c fun2.c -> 依赖
main -> 目标
2、makefile书写规则?
======================无依赖=================
1)确定目标叫什么名字。
2)按照以下的规则来写makefile:
目标:
<Tab键>执行规则
例子: 使用makefile来写一个helloworld程序。
------------------------------------------------
target:
echo helloworld
结果:
gec@ubuntu:/mnt/hgfs/GZ2057/04 Makefile/code$ make
echo helloworld
helloworld
gec@ubuntu:/mnt/hgfs/GZ2057/04 Makefile/code$
---------------------------------------------------
target:
@echo helloworld -> 加多一个@,在执行make时,就会隐藏执行规则。
结果:
gec@ubuntu:/mnt/hgfs/GZ2057/04 Makefile/code$ make
helloworld
gec@ubuntu:/mnt/hgfs/GZ2057/04 Makefile/code$
=========================有依赖=========================
1)确定目标叫什么名字。
2)按照以下的规则来写makefile:
目标:依赖(如果有多个依赖,则每一个依赖之间使用空格分开)
<Tab键>执行规则
例子:使用makefile去编译一个简单的程序。
hello:hello.c
gcc $^ -o $@ ($^ -> 代表所有的依赖)
($@ -> 代表目标)
结果:
gec@ubuntu:/mnt/hgfs/GZ2057/04 Makefile/code$ make
gcc hello.c -o hello
gec@ubuntu:/mnt/hgfs/GZ2057/04 Makefile/code$ ls
diff easy hello -> 确实当前的目录下生成了hello文件。
hello.c makefile
练习1:完成more_c/目录中的makefile。
--------------------------------
test:test.c fun1.c fun2.c
gcc $^ -o $@
--------------------------------
3、 重复执行make命令,那么会出现什么情况?
第一次make:
gec@ubuntu:/mnt/hgfs/GZ2057/04 Makefile/code/more_c$ make
gcc test.c fun1.c fun2.c -o test
第二次make:
gec@ubuntu:/mnt/hgfs/GZ2057/04 Makefile/code/more_c$ make
make: 'test' is up to date. -> 当前test文件已经是最新的。
makefile编译规则:
在makefile编译之前,都会检测所有的依赖对应的修改时间是否与上次编译的修改时间一致,如果所有的依赖都没有修改,则不会进行编译,如果有一个依赖的时间被修改过,则makefile就会再次编译。
练习2: 完成复杂版的makefile,只需要执行make,就可以在bin/目录下看到目标程序。
bin/test:main/test.c src/fun1.c src/fun2.c
gcc $^ -o $@ -I ./include/
四、makefile多个目标情况。
例子:
target1:
xxxx
target2:
yyyy
target3:
zzzz
执行make -> 默认执行第一个目标 等价于 make target1
执行make target2 -> 指定执行第二个目标,即target2。
例题3:使用复杂版:
当执行make时,就编译工程,生成可执行文件。
当执行make clean时,就删除可执行文件。
bin/test:main/test.c src/fun1.c src/fun2.c
gcc $^ -o $@ -I ./include/
clean:
rm bin/test
五、makefile变量种类?
1、自定义变量。
在makefile中定义自定义变量,不需要声明数据类型,只需要定义名字就可以了,因为在makefile中,所有的变量都是默认是字符串类型。
A -> 默认就是字符串类型的变量。
规定:
1)变量名命名规则与C语言一致。
2)给变量赋值,等号两边可以有空格,可以没有空格。 -> shell编程一定没有。
A=Hello
A = Hello
3)引用变量的值时,需要在变量的前面添加一个$。
makefile引用变量时,需要给变量添加一个()。
A = Hello
B = $(A) World
4)因为变量都是字符串类型,所以""可以省略。
A = Hello
A = "Hello"
练习4:将目标与依赖保存在变量中,写规则,我们只需要引用变量,等价于引用依赖与目标。
TARGET=bin/test
C_SOURCE=main/test.c src/fun1.c src/fun2.c
INCLUDE_PATH=-I ./include/
$(TARGET):$(C_SOURCE)
gcc $^ -o $@ $(INCLUDE_PATH)
clean:
rm bin/test
2、系统预设定变量
有的变量在系统中已经写好的了,并且已经赋好值,我们直接引用就可以。
CC -> 编译器名字,默认等于cc cc等价于gcc 默认: CC=gcc
RM -> 删除命令,默认等于rm -f 默认: RM=rm -f (-f:不可忽略文件)
这些变量如果不赋值,就按照默认的值来处理,但是这些变量也是可以赋值的,如果赋值了,就按新的值来处理。
例子4: 将系统预设定变量加入到Makefile中。
CC=arm-linux-gcc
TARGET=bin/test
C_SOURCE=main/test.c src/fun1.c src/fun2.c
INCLUDE_PATH=-I ./include/
$(TARGET):$(C_SOURCE)
$(CC) $^ -o $@ $(INCLUDE_PATH)
clean:
$(RM) bin/test
3、自动化变量。 -> 变量的值不是固定,而是变化的。
$^ -> 代表所有的依赖。
$@ -> 代表目标。
六、makefile伪指令。
场景一:假设makefile中有一套规则:
---------------------------------
clean:
$(RM) bin/test
---------------------------------
当我们执行make clean时,makefile就会执行这套规则。
场景二:
假设makefile中有一套规则,并且当前目录下有一个目录名字叫clean/:
---------------------------------
clean:
$(RM) bin/test
---------------------------------
当我们执行make clean时:
make: 'clean' is up to date. -> 编译器以为clean是一个生成的文件,与当前目录下的clean重名。
1)如何告诉编译器,这是一套规则,而不是一个生成的文件?
解决方案:使用伪指令。
2)怎么使用伪指令?
只需要在makefile中添加一句话即可。
.PHONY:clean
----------------------------------------------
CC=arm-linux-gcc
TARGET=bin/test
C_SOURCE=main/test.c src/fun1.c src/fun2.c
INCLUDE_PATH=-I ./include/
$(TARGET):$(C_SOURCE)
$(CC) $^ -o $@ $(INCLUDE_PATH)
.PHONY:clean
clean:
$(RM) bin/test
----------------------------------------------
七、makefile函数。
wildcard
功能:就是在指定的路径下寻找匹配的文件。
C语言函数调用:函数名(参数1,参数2,参数3,...)
makefile函数调用: $(函数名 参数1,参数2,....)
例子:我想把当前目录下的所有的.c文件的名字都找出来,并且将这些文件名保存到一个变量。
C_SOURCE = $(wildcard ./*.c)
例题5:将函数加入到makefile中。
/* 通用版本的makefile */
CC=arm-linux-gcc
TARGET=bin/test
C_SOURCE=$(wildcard ./main/*.c ./src/*.c)
INCLUDE_PATH=-I ./include/
$(TARGET):$(C_SOURCE)
$(CC) $^ -o $@ $(INCLUDE_PATH)
.PHONY:clean
clean:
$(RM) $(TARGET)
八、嵌入式linux库文件概念。
1、什么是库文件?
在linux下,有非常多的目录都有库文件。
例如: /lib -> linux系统库文件,一般都是linux自带。
/usr/lib -> 有的是系统自带的库文件,有的是第三方程序移植过来的。
/usr/local/lib -> 用户自己移植的库文件。
/usr/local/arm/5.4.0/usr/lib -> 交叉工具链的库文件的所在地。
2、究竟库文件存放着什么数据?
1)因为库文件是一个二进制的文件,所以人类是看不懂,只能看到一片乱码。
2)库文件里面存放的数据,其实都是一些函数来的。
功能程序 处理 一个可以代表这些功能函数的库文件
fun1.c fun2.c -------------> libxxxxx.so
fun3.c fun4.c
3、为什么在linux下,库叫做lib?
lib -> library -> 图书馆(很多书) -> 库(很多函数) -> library -> lib
shell -> 贝壳(外面->用户 里面->内核) -> 外面访问里面(需要外壳) -> 用户访问内核(需要命令) -> shell
socket -> 插座(充电器 -> 协议1 插座样子 -> 协议2) -> 充电器必须与插座样子一样才能充电 -> 协议1与协议2必须一致才能通信
-> 网络通信也是必须协议一致,才可以通信。
九、在一个项目中,做库文件有什么好处?
1、可以保护代码的安全性。
项目已经完成了 卖给客户 执行
fun1.c fun2.c fun3.c ------------> fun1.c fun2.c fun3.c --------> 源码和效果
项目已经完成了 制作成库文件 卖给客户 执行
fun1.c fun2.c fun3.c ------------> libxxx.so -------> libxxx.so ------> 效果
2、可以让代码变得非常简单。 -> 只需要在main函数中调用那个库里面的接口就可以。
十、如何制作库文件?
1、库文件的格式。
1)静态库 -> libxxxx.a
2)动态库(共享库) -> libxxxx.so
例如:
libmpfr.so.4.1.5
lib -> 库的前缀
mpfr -> 库的名字
.so -> 库的后缀
.4 -> 库的版本号
.1.5 -> 库的修正号
2、静态库与动态库有什么区别?
静态库:libxxxx.a -> 你(main函数)去(调用)图书馆(库)把图书(函数)借走
1)程序在编译时,如果使用是静态库,等价于把库里面的东西拿走,这样就会导致编译后的可执行程序非常大。
2)由于使用静态库,所以编译完之后,无论库文件在还是不在,可执行程序都可以正常。
动态库: libxxxx.so -> 你(main函数)去(调用)图书馆(库)只是看看书(函数)而已,看完放回原位就走了。
1)程序在编译时,如果使用动态库,那么仅仅是链接而已,没有拿走库里面的东西,相对于静态库而言,文件比较小。
2)由于使用动态库,所以执行可执行程序时,动态库必须存在,否则可执行程序就不能正常执行。
===========================================================
3、制作静态库?
1)制作项目源码: test.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
4)编译程序。
gcc test.c -o test -I . -L . -lmy_ggy
-I . -> 编译时去当前目录下寻找头文件。 -> 因为程序中已经写好包含my_head.h
-L . -> 编译时去当前目录下寻找库文件。 -> 没有指定找那个库,所以还需要-l来进一步说明。
-lmy_ggy-> -l后面紧跟着库的名字
5)无论库在,还是不在,都可以执行。
./test -> 8752
=============================================================
4、制作动态库?
1)制作项目源码: test.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 test.c -o test -I . -L . -lggy
5)执行程序。
./test -> 8624
./test: error while loading shared libraries: libggy.so: cannot open shared object file: No such file or directory
//执行程序时,需要加载libggy.so这个库,但是有文件找不到,所以不能打开这个库。
解决方案:将这个库的路径设置到LD_LIBRARY_PATH这个环境变量中。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/gec -> 如果需要永久设置,则把这句话写入到脚本中。
练习6: 如果动态库与静态库同时存在,那么编译程序时,是链接静态库还是动态库?
十、研究库的架构。
0、我们能不能使用gcc来编译功能程序,然后使用gcc来编译代码。
可以,程序可以正常在ubuntu运行。
1、我们能不能使用gcc来编译功能程序,然后使用arm-linux-gcc来编译代码。
不可以。
./libmy.a: error adding symbols: File format not recognized
-> 库是gcc的,使用arm-linux-gcc编译程序,所以识别不了库的格式。
2、我们能不能使用arm-linux-gcc来编译功能程序,然后使用gcc来编译代码。
不可以。
/usr/bin/ld: skipping incompatible ./libmy.a when searching for -lmy
/usr/bin/ld: cannot find -lmy
-> 库是arm-linux-gcc的,使用gcc编译程序,所以识别不了库的格式。
3、我们能不能使用arm-linux-gcc来编译功能程序,然后使用arm-linux-gcc来编译代码。
可以,程序可以正常在开发板运行。
十一、将库文件加入到makefile中。
1、先将编译命令整理到makefile中先。
------------------------------------------------------
CC=arm-linux-gcc
TARGET=bin/test
C_SOURCE=$(wildcard ./src/*.c) -> 不含main函数在内的.c文件
MAIN_SOURCE=$(wildcard ./main/*.c) -> 含有main函数在内的.c文件
INCLUDE_PATH=-I ./include/
LIB_PATH=-L ./lib -lmy
$(TARGET):$(MAIN_SOURCE) -> 含有main函数在内的.c文件。
$(CC) $^ -o $@ $(INCLUDE_PATH) $(LIB_PATH)
.PHONY:clean
clean:
$(RM) $(TARGET)
2、makefile函数
patsubst
作用: 修改文件的后缀,并将这些改完的名字放置到一个变量。
(没有生成任何的新的文件)
例如:假设有一个文件叫ggy.c,那么patsubst就可以搞一个新的名字出来,叫做ggy.o,没有生成这个ggy.o这个文件,只是搞了一个名字出来而已。
C_SOURCE=$(wildcard ./src/*.c) -> 不含main函数在内的.c文件
OBJ=$(patsubst %.c,%.o,$(C_SOURCE)) -> 把C_SOURCE里面的文件,只要是.c结尾的,就搞一个新的.o的文件名。
%.o:%.c
$(CC) $^ -o $@ -c -fPIC -> 将对应的.c生成对应的.o文件。
$(LIB_PATH):$(OBJ)
$(CC) -fPIC -shared -o $@ $^
前两句的理解:
C_SOURCE = fun1.c fun2.c
OBJ = fun1.o fun2.o -> 这只是一些名字,保存在OBJ中。
中间一句的理解:
fun1.o:fun1.c
gcc fun1.c -o fun1.o -c -fPIC
fun2.o:fun2.c
gcc fun2.c -o fun2.o -c -fPIC
后面这句话的理解:
gcc -fPIC -shared -o ./lib/libmy.so fun1.o fun2.o
最后的通用万能版本:
CC=gcc
TARGET=bin/test
LIB_NAME=./lib/libmy.so
MAIN_SOURCE=$(wildcard ./main/*.c)
INCLUDE_PATH=-I ./include/
LIB_PATH=-L ./lib -lmy
C_SOURCE=$(wildcard ./src/*.c)
OBJ=$(patsubst %.c,%.o,$(C_SOURCE))
%.o:%.c
$(CC) $^ -o $@ -c -fPIC $(INCLUDE_PATH)
$(LIB_NAME):$(OBJ)
$(CC) -fPIC -shared -o $@ $^
bin/test:$(MAIN_SOURCE)
$(CC) $^ -o $@ $(INCLUDE_PATH) $(LIB_PATH)
.PHONY:clean
clean:
$(RM) $(TARGET) $(LIB_NAME) ./src/*.o
结果:
执行make,就可以生成库文件。
执行make bin/test,就可以生成可执行文件。