赞
踩
注:本人已购买韦东山老师第三期项目视频,内容来源《数码相框项目视频》,只用于学习记录,如有侵权,请联系删除。
(1) 一个C/C++程序要经过预处理、编译、汇编、链接 4个步骤才可以变成可执行文件:
注:上面的 预处理 、编译、汇编这三个步骤就是我们常说的编译。
(2) 对于以下代码:
a.h 代码:
#define A 1
a.c 代码:
#include <stdio.h>
#include "a.h"
int main(void)
{
printf("Hello world!\n");
printf("A = %d\n", A);
test_fun(); /* 调用 b.c 的 test_fun 函数 */
return 0;
}
b.c 代码:
#include <stdio.h>
int test_fun(void)
{
printf("it is B\n");
return 0;
}
编译上面a.h、a.c、b.c代码的方法:
① gcc -o test a.c b.c
执行./test
命令调用结果如下:
那么gcc -o test a.c b.c
做了哪些事情?
使用这边编译方法的优缺点:
② 写Makefile
目标:依赖1 依赖2 ...
<TAB> 命令
test: a.c b.c a.h
gcc -o test a.c b.c
gcc -o test a.c b.c
重新编译;修改b.c、a.h 同理。可见,只要其中一个依赖修改了,就会执行对应的命令重新编译出目标文件。gcc -o test a.c b.c
,只有一个依赖文件修改了,其他所有的依赖文件都会重新编译,效率低。① 改进方案一:Makefile 代码如下:
test:a.o b.o
gcc -o test a.o b.o
# -c:表示只预处理、编译、汇编,不链接
a.o:a.c
gcc -c -o a.o a.c
b.o:b.c
gcc -c -o b.o b.c
gcc -c -o a.o a.c
命令生成 a.o 目标文件;对于第二个依赖 b.o 与 a.o 同理;当 a.o、b.o 都生成后,就使用gcc -o test a.o b.o
。总的来说就是先执行gcc -c -o a.o a.c
命令,再执行gcc -c -o b.o b.c
命令,最后执行gcc -o test a.o b.o
命令。gcc -c -o a.o a.c
,而新生成的 a.o 比 test新,所以执行gcc -o test a.o b.o
重新生成 test 目标文件。gcc -c -o a.o a.c
这样的命令,效率低。改进方法:使用通配符%。 改进的Makefile代码如下:test:a.o b.o
gcc -o test a.o b.o
# $@:表示目标
# $<:表示第一个依赖
# $^:表示所有的依赖
%.o:%.c
gcc -c -o $@ $<
#define A 1
修改为#define A 2
,重新编译,运行,运行结果如下图所示:可见,A 依然还是修改前的 1。test:a.o b.o
gcc -o test a.o b.o
a.o:a.c a.h
%.o:%.c
gcc -c -o $@ $<
#define A 1
,第二次:#define A 2
)修改a.h 后重新编译,编译运行结果如下图所示:可见,修改头文件也能重新编译对应的.c 源文件了。a.o : a.c a.h
,当头文件很多的时候,显然也有编写效率低的问题,那么有什么自动的规则呢?利用gcc生成依赖文件:gcc -Wp,-MD,$@.d -c -o $@ $<
,其中$@.d
是生成的依赖文件。修改的Makefile代码如下:objs := a.o b.o test:$(objs) gcc -o test $^ # dep_files := .a.o.d .b.o.d dep_files := $(foreach f,$(objs),.$(f).d) # 对于objs里的每一个成员都使用.$(f).d来替换 dep_files := $(wildcard $(dep_files)) # wildcard 取出所有符合$(dep_files)格式的存在的文件 # 如果dep_files变量不为空,则包含dep_files变量的文件 ifneq ($(dep_files),) include $(dep_files) endif %.o:%.c gcc -Wp,-MD,.$@.d -c -o $@ $< clean: rm -rf *.o test
回顾以前数码相框(六、在LCD上显示任意编码的文本文件)的Makefile,它有一个缺陷就是当我们修改某个 .h 头文件之后,对应的 .c 源文件不能够重新编译。这一小节将以电子书的工程文件为基础,仿照linux内核的Makefile编写一个支持工程文件的通用Makefile。
步骤:
① 在每个子目录下建立一个Makefile,子目录的Makefile的内容如下:
obj-y += file1.o
obj-y += file2.o
...
其中,file1.o、file2.o 是涉及的 .c 源文件对应的 .o 文件。
② 假如有子目录下又有子目录,子目录的 Makefile 如下:
obj-y += file1.o
obj-y += file2.o
obj-y += test/ # test是子目录下的子目录
...
那么,test目录下的 Makefile 如下:(假设test目录下有 test.c 源文件)
obj-y += test.o
③ 编写顶层目录下的 Makefile:
# 工具链 CROSS_COMPILE = arm-linux- AS = $(CROSS_COMPILE)as LD = $(CROSS_COMPILE)ld CC = $(CROSS_COMPILE)gcc CPP = $(CC) -E AR = $(CROSS_COMPILE)ar NM = $(CROSS_COMPILE)nm STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump # 导出变量 export AS LD CC CPP AR NM export STRIP OBJCOPY OBJDUMP # 指定编译参数: -Wall:开启全部警告; -O2: 优化选项; -g: 加上调试信息 CFLAGS := -Wall -O2 -g # 指定编译头文件目录 (为什么要指定编译目录?假如没有指定头文件目录,编译器自动到系统 /usr/include 目录寻找头文件,例如stdio.h,当使用一个编译器时,编译器会默认有一个系统目录。那么我们是否能够指定头文件目录呢?使用 -I 选项指定头文件目录,格式:-I 头文件目录;同样,链接的时候加上 -L 选项指定库文件目录,格式:-L 库文件目录) CFLAGS += -I $(shell pwd)/include # 指定连接参数: -lm: 表示数学库; -lfreetype: 表示freetype库 LDFALGS := -lm -lfreetype # 导出 CFLAGS LDFALGS export CFLAGS LDFALGS TOPDIR := $(shell pwd) export TOPDIR # = 表示延时变量,它的值只有使用的时候,才可以确定。它最大的缺点是不能在变量后面追加内容。 # := 表示立即变量,它的值立马确定 # 最终编译出来的目标文件 show_file TARGET := show_file obj-y += main.o obj-y += display/ obj-y += draw/ obj-y += encoding/ obj-y += fonts/ # 第一个规则 all : # 进入当前目录使用 Makefile.build 来编译 make -C ./ -f $(TOPDIR)/Makefile.build $(CC) $(LDFALGS) -o $(TARGET) built-in.o # 清除 clean: rm -rf $(shell find -name "*.o") rm -rf $(TARGET) distclean: rm -rf $(shell find -name "*.o") rm -rf $(shell find -name "*.d") rm -rf $(TARGET)
到此,顶层目录的Makefile已经写完,可见,这个Makefile 严重依赖于 Makefile.build 这个文件,接下来需要写出 Makefile.build。
show_file 工程目录如下:
show_file 工程编译思路:
④ Makefile.build 编写:
# 假目标, 直接make生成第一个目标 PHONY := __build __build: # obj-y 赋空值 obj-y := # 子目录 subdir-y := # 包含当前目录的 Makefile include Makefile # 取出子目录 # obj-y := a.o b.o c/ d/ # $(filter %/, $(obj-y)) = c/ d/ # __subdir-y := c d # subdir-y += c d __subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) subdir-y += $(__subdir-y) # c/built-in.o d/built-in.o subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o) # cur_objs := a.o b.o cur_objs := $(filter-out %/, $(obj-y)) # 依赖文件 dep_files := $(foreach f,$(cur_objs),.$(f).d) dep_files := $(wildcard $(dep_files)) # 如果dep_files变量不为空,则包含dep_files变量的文件 ifneq ($(dep_files),) include $(dep_files) endif PHONY += $(subdir-y) __build:$(subdir-y) built-in.o # 进入子目录编译 $(subdir-y): # 进入子目录, 使用 Makefile.build 来编译 make -C $@ -f $(TOPDIR)/Makefile.build built-in.o: $(cur_objs) $(subdir_objs) $(LD) -r -o $@ $^ dep_file = .$@.d %.o : %.c $(CC) $(CFLAGS) -Wp,-MD, $(dep_file) -c -o $@ $< .PHONY : $(PHONY)
⑤ 编写好Makefile 后,重新编译 show_file 工程,编译结果如下图所示:
⑥ 我们在test目录下添加 test.h,重新编译,编译结果如下图所示:可见,只重新编译了test.c,然后重新打包、链接成 show_file目标文件。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。