一、工程管理文件makefile。
1. 什么是makefile?
makefile称之为工程管理文件,用于管理整个工程所有.c文件编译规则。
2. makefile是一个工程中是一定要写的吗?
如果在项目源码中,文件不多的时候,一般makefile不用写,因为编译命令比较简单。
如果在项目源码,源文件(.c) 头文件(.h)这些文件比较多,一般会携带一个makefile。
写makefile目的: 为了提高编译效率。
二. 项目工程应该由哪些文件组成?
1. 简单版
源程序文件main函数在内的.c文件。
功能程序文件1个功能文件 play_music.c
功能程序文件2个功能文件
...
功能程序文件n个功能文件
头文件:..
库文件:
工程管理文件: makefile -> 里面有一套可以编译整个项目的规则。
2. 复杂版
src/ -> 所有的.c文件
include/ -> 所有的.h文件
lib/ -> 所有的库文件
bin/ -> 编译之后的可执行文件
makefile -> 里面有一套可以编译整个项目的规则。
如果没有makefile,上述例子中,用户应该每次都要输入: "gcc main.c fun1.c fun2.c -o main -I ."
学习书写makefile之后,就可以让makefile帮我们做这件事情了。
三. makefile书写规则?
1. makefile文件的核心:"依赖" 与 "目标"?
"依赖" -> 在编译文件时,才需要写依赖,如果不是编译,则不需要写依赖。一般依赖指的就是.c文件。
"目标" -> 你最终想得到的文件,一般指的是可执行程序
main.c fun1.c fun2.c -> 依赖
main -> 目标
2. makefile的书写规则?
有依赖情况下:
========================================================
1)确定好依赖与目标叫什么名字。
2)按照以下格式来写makefile:
目标:依赖(如果有多个依赖,则每一个依赖之间使用空格分开)
<Tab键>编译规则
注意:
1)<Tab键>不等于四个空格,编译规则之所以能识别出来,就是因为这个Tab键
2)在编译规则中如何调用依赖与目标
main.c fun1.c fun2.c
main
无依赖情况下:
1)确定目标叫什么名字。
2)按照以下规则来写makefile:
目标:
<Tab键>执行命令
3. 举实例。
1)makefile的helloworld版本1。
target: //目标一定要写,但是依赖不一定。
make命令,就会执行这套规则了。
执行make,结果:
echo helloworld -> 默认将规则命令输出到屏幕中,如果不想看到,则只需要在规则前面加@
helloworld
2)makefile的helloworld版本2。
target: //目标一定要写,但是依赖不一定。
make命令,就会执行这套规则了。
执行make,结果:
helloworld
code1的代码,写一个makefile,使得执行make时候,可以生成main这个可执行程序。
main:main.c fun1.c fun2.c
gcc $^ -o $@
执行make,结果:
gcc main.c fun1.c fun2.c -o main -> 在当前目录下生成一个main文件。
再次执行make,结果:
make: `main' is up to date. -> 因为makefile检测到所有的依赖文件都没有更新,所以不会重新编译。
-> 只要其中一个依赖文件的修改日期与之前的不一样,都会重新编译。
四、makefile多个目标情况。
例子:
main: -> 当执行make,默认只会执行第一个目标。
xxxx;
main1: -> 当需要执行某个特定的目标时,在make时候,需要指定该目标的名字: make main1
yyyy;
main2:
zzzz;
修改makefile为:
main:main.c fun1.c fun2.c
gcc $^ -o $@
clean:
rm main
五. makefile的变量种类。
1. 自定义变量 -> makefile不需要声明类型,只需要定义名字就可以。变量默认是字符串类型的。
规则:
1)变量名与C语言规则一致。
2)给变量赋值时,等号两边都可以有空格,也可以没有。但是shell编程中变量赋值时,等边两边不能有空格。
3)引用makefile中变量时,需要在变量前面添加$。 $A $(A)
4)因为变量都是默认是字符串类型,所以""可以省略。
例子:
A = Hello 等价于 A = "hello"
B=world
C=$A $(B)
2: 修改makefile,将所有的依赖放置到一个变量。
C_SOURCE=main.c fun1.c fun2.c
main:$(C_SOURCE)
gcc $^ -o $@
clean:
rm main
2. 系统预设定变量
有些变量是系统中已经写好的,并且已经赋了初值的,这些变量的值就可以直接使用。
CC:cc CC=cc cc等价于gcc
RM:rm -f RM=rm -f
练习3:把系统预设定变量加入到makefile中。尝试交叉编译与本地编译。
CC=arm-linux-gcc
C_SOURCE=main.c fun1.c fun2.c
main:$(C_SOURCE)
$(CC) $^ -o $@
clean:
rm main
3. 自动化变量 -> 变量的值不是固定的,而是会变化的。
$^ -> 代表当前的所有依赖
$@ -> 代表目标
例子:
main:
main
clean:
clean
例子:
CC=arm-linux-gcc
C_SOURCE=main.c fun1.c fun2.c
TARGET=main
$(TARGET):$(C_SOURCE)
$(CC) $^ -o $@
clean:
rm $(TARGET)
六. makefile伪指令。
假设makefile有一套规则:
------------------------------
clean:
$(RM) main
------------------------------
当我们执行make clean时,就会执行这套规则,如果当前目录下有一个文件名字叫clean
那么再执行make clean就会提示:make: `clean' is up to date.
解决方案:将clean这个目标添加为伪指令。
添加为伪指令的含义是什么?
就是告诉makefile,这个目标不是一个生成的文件。
在makefile中添加代码:
.PHONY:clean
-----------------------------------------
CC=arm-linux-gcc
C_SOURCE=main.c fun1.c fun2.c
TARGET=main
$(TARGET):$(C_SOURCE)
$(CC) $^ -o $@
.PHONY:clean
clean:
$(RM) $(TARGET)
----------------------------------------
七. makefile函数 -> wildcard
1. makefile中调用函数方式: $(函数名参数1,参数2,参数3.....)
(参数1,参数2,参数3...)
2. wildcard函数作用:在指定的路径下找到相匹配的文件
例子:.c结尾的文件,并把结果保存在SRC变量,每个结果之间使用空格分开。
.c结尾的文件。
注意:
* -> 代表任意长度的任意字符。
最终得到简单版通用的makefile为:
CC=arm-linux-gcc
C_SOURCE=$(wildcard *.c)
TARGET=main
INCLUDE_PATH=-I .
$(TARGET):$(C_SOURCE)
$(CC) $^ -o $@ $(INCLUDE_PATH)
.PHONY:clean
clean:
$(RM) $(TARGET)
4: 将简单版通用的makefile修改为复杂版的!
CC=arm-linux-gcc
C_SOUCRE=$(wildcard ./src/*.c)
TARGET=./bin/main
INCLUDE_PATH=-I ./include
$(TARGET):$(C_SOUCRE)
$(CC) $^ -o $@ $(INCLUDE_PATH)
.PHONY:clean
clean:
$(RM) $(TARGET)
八.嵌入式linux的库文件。
1. 什么是库文件?
库文件在linux下以二进制形式存在,往往我们编译程序时,需要链接这些库。
2. 库文件的格式?
1)静态库 ---> libxxx.a
2)动态库/共享库 ---> libxxx.so
libxxx.so.9.1.0
lib: 库的前缀
xxx: 库的名字
.a/.so: 库的后缀
.9: 库的版本号
.1.0:库的修正号
3. 静态库与动态库的区别?
静态库特点:libxxxx.a -> 去图书馆(libxxxx.a)中把图书(函数接口)拿走
1)程序在编译时,如果是链接静态库,那么就等于把库的内容拿走,就会导致可执行程序的大小非常大。
2)由于是静态库编译,所以在编译程序之后,可执行文件不需要静态库的存在都可以运行。
动态库特点:libxxx.so -> 去图书馆(libxxx.so)看看书(函数接口)而已,看完了,就把书放回图书馆,并没有拿走。
1)程序在编译时,如果是链接动态库,仅仅链接而已,没有拿走库的东西,相对于静态库来讲,会比较小。
2)由于是动态库编译,在执行可执行文件之后,动态库必须存在,可执行文件才能正常运行。
4. 如何制作库文件?
只有包含功能性函数.c文件才能制作成库文件,含有main函数在内的.c文件不能制作为库文件。
1)静态库的制作。 -> 没有架构可言。
1.将工程所有不包含main函数在内的.c文件找到。
2.将这些.c文件全部编译为.o文件。
gcc fun1.c -o fun1.o -c
gcc fun2.c -o fun2.o -c
3.将这些.o文件全部塞进一个.a文件
ar rcs libmy.a fun1.o fun2.o
4.编译程序
gcc main.c -o main -L . -lmy
5.执行
./main 就可以出来结果!
注意:
1)-L . -> 只是告诉系统去当前目录下寻找库文件,但是没有告诉链接哪个。
2)-lmy -> -l(小写字母L)没有空格的,要紧跟库的名字(my),注意不是库文件的名字(libmy.a)
3)制作库与编程程序所使用到的工具链必须一致。
5:熟悉交叉编译静态库的制作。
arm-linux-gcc fun1.c -o fun1.o -c
arm-linux-gcc fun2.c -o fun2.o -c
ar rcs libmy.a fun1.o fun2.o
arm-linux-gcc main.c -o main -L . -lmy
结论: 库的架构必须与编译程序时的工具链架构一致,否则就会出错:
库:x86 编译: arm-linux-gcc
/usr/local/arm/bin/../lib/gcc/arm-none-linux-gnueabi/4.5.1/../../../../arm-none-linux-gnueabi/bin/ld: skipping incompatible ./libmy.a when searching for -lmy
/usr/local/arm/bin/../lib/gcc/arm-none-linux-gnueabi/4.5.1/../../../../arm-none-linux-gnueabi/bin/ld: cannot find -lmy
库: gcc
/usr/bin/ld: skipping incompatible ./libmy.a when searching for -lmy
/usr/bin/ld: cannot find -lmy
collect2: ld returned 1 exit status
6:把库添加到复杂版的lib目录,修改makefile。
CC=arm-linux-gcc
C_SOUCRE=$(wildcard ./src/*.c)
TARGET=./bin/main
INCLUDE_PATH=-I ./include
LIBRARY_PATH=-L ./lib -lmy
$(TARGET):$(C_SOUCRE)
$(CC) $^ -o $@ $(INCLUDE_PATH) $(LIBRARY_PATH)
.PHONY:clean
clean:
$(RM) $(TARGET)
2)动态库的制作 -> 有架构可言
1. 将工程所有不包含main函数在内的.c文件找到。
2. 将这些.c文件全部编译为.o文件。
gcc fun1.c -o fun1.o -c -fPIC
gcc fun2.c -o fun2.o -c -fPIC
3. 将这些.o文件编译为libxxx.so文件
gcc -shared -fPIC -o libmy2.so fun1.o fun2.o
4. 编译程序
gcc main.c -o main -L . -lmy2
5. 执行
./main
终端提示错误:
./main: error while loading shared libraries: libmy2.so: cannot open shared object file: No such file or directory
//执行main程序时需要加载共享库:libmy2.so时出现了错误: 因为文件不存在所以,不能打开这个文件。
解决方案:
1. 把制作的好的库文件拷贝到/lib中 -> 不推荐
2. 把该库的路径添加到环境变量 LD_LIBRARY_PATH
/home/gec。
"libmy2.so"这个东东!
3. 重新执行代码
./main
7:熟悉交叉编译动态库的制作。
8:把库添加到复杂版的lib目录,修改makefile。