前面我们学了很多Makefile相关的知识,但是没有写过一个完整的代码,这一章我们写出一个实例

一、完善Makefile

在之前我们写了一个较为完善的Makefile程序,但是还是存在一些问题,我们需要慢慢完善程序。相关程序如下:
main.c

#include <stdio.h>
#include "sub.h"int main(int argc, char *argv[])
{int i;printf("Main fun!\n");sub_fun();return 0;
}

sub.c

#include "sub.h"void sub_fun(void)
{printf("Sub fun! A=%d\n", A);
}

sub.h

#define A    2void sub_fun(void);

Makefile

test : main.o sub.ogcc -o test $^
%.o : %.cgcc -c -o $@ $<
clean:rm *.o test.PHONY: clean

运行结果:

由上述程序可知,sub.c中使用了A的宏命令,我们在sub.h改变A的值,再次编译:

发现提示:test没有更新,这是为什么呢?
这是因为我们在Makefile中没有依赖sub.h,无论sub.h 怎么变化,sub.o都不会改变,导致test没有更新,那么我们应该在Makefile中加入依赖代码,如下:

test : main.o sub.ogcc -o test $^
sub.o : sub.c sub.h
%.o : %.cgcc -c -o $@ $<
clean:rm *.o test.PHONY: clean

当A改为3时,运行结果为:

那么每次新增了头文件,是不是得我们手动添加呢?显然这样是不高效的,C/C++的编译器提供了自动获取头文件依赖的方法。

二、自动获取头文件的依赖关系

1. 基础知识

1.1 -M选项

GCC的-M选项生成文件的依赖关系,同时也把一些标准库的头文件包含了进来。本质是告诉预处理器输出一个适合 make 的规则,用于描述各目标文件的依赖关系。对于每个源文件,预处理器输出 一个 make 规则,该规则的目标项 (target) 是源文件对应的目标文件名,依赖项 (dependency) 是源文件中 “#include” 引用的所有文件,生成的规则可以是单行,但如果太长,就用’'换行符续成多行。规则显示在标准输出,不产生预处理过的 C 程序。注意:该选项默认打开了 -E 选项, -E 参数的用处是使得编译器在预处理结束时就停止编译。
例如,执行下面的命令:

gcc -M main.c

输出如下:

由编译器自动生成依赖关系,这样做的好处有以下几点:

  • 不必手动书写若干目标文件的依赖关系,由编译器自动生成
  • 不管是源文件还是头文件有更新,目标文件都会重新编译

1.2 -MM选项

生成文件的依赖关系,和 -M 类似,但不包含标准库的头文件
例如:
···
gcc -MM main.c
···
输出结果:

1.3 -MG选项

要求把缺失的头文件按存在对待,并且假定他们和源文件在同一目录下,必须和 ‘-M’ 选项一起用。

1.4 -MF File

必须和 ‘-M’ 或’-MM’选项一起用,当使用了 “-M” 或者 “-MM” 选项时,则把依赖关系写入名为 “File” 的文件中。若同时也使用了 “-MD” 或 “-MMD”,“-MF” 将覆写输出的依赖文件的名称 。
例如:

 gcc -M -MF main.d main.c

输出结果:

则 “-M” 输出的内容就保存在 main.d 文件中了。

1.5 -MD或者-MMD

等同于 -M -MF File(或者 -MM -MF File),但是默认关闭了 -E 选项。其输出的文件名是基于 -o 选项,若给定了 -o 选项,则输出的文件名是 -o 指定的文件名,并添加 .d 后缀,若没有给定,则输入的文件名作为输出的文件名,并添加 .d 后缀,同时继续指定的编译工作。
注意:-MD 不会像 -M 那样阻止正常的编译任务,因为它默认关闭了 -E 选项,比如命令中使用了 -c 选项,其结果要生成 .o 文件,若使用了 -M 选项,则不会生成 .o 文件,若使用的是 -MD 选项,则会生成 .o 文件,有没有关闭-E,只要看窗口是否输出编译信息。
例如:

gcc -E -MD main.c

输出:

本目录下生成了以下文件:
main.d
同时在终端上输出了 main.c 文件的预处理结果

例如:

gcc -E -o tmp.i -MD main.c

本目录下生成了以下文件:
tmp.d tmp.i

例如:

gcc -c -MD main.c

本目录下生成了以下文件:
main.d main.o

例如:

gcc -c -o tmp.o -MD main.c

本目录下生成了以下文件:
tmp.d tmp.o

2. 继续优化Makefile

如果我们想编译生成main.o的同时且把依赖写入到main.d,代码如下:

gcc -c -o main.o main.c -MD -MF main.d

输出如下:

我们继续优化Makefile,一般.d文件可以不用被用户看到,我们设置为隐藏(Linux如果要将一个文件隐藏,只需在前面加上. , 此时使用ls,或者ls -l查看不到,只能使用ls -a才能查看到)
优化后的Makefile如下:


test : main.o sub.ogcc -o test $^
sub.o : sub.c sub.h
%.o : %.cgcc -c -o $@ $< -MD -MF .$@.d
clean:rm *.o test.PHONY: clean

运行结果图:

可以看到生成我们需要的 .main.o.d .sub.o.d文件

3. 比较完善的Makefile

实例代码如下:


objs = main.o sub.odep_files := $(patsubst %, .%.d, $(objs))
dep_files := $(wildcard $(dep_files))test : $(objs)gcc -o test $^ifneq ($(dep_files),)
include $(dep_files)
endif%.o : %.cgcc -c -o $@ $< -MD -MF .$@.d
clean:rm *.o testdistclean:rm $(dep_files)
.PHONY: clean

运行结果:

代码解释:

  • 为了方便书写依赖的对象文件,程序使用objs 即时变量存储OBJ文件
  • dep_files := $(patsubst %, .%.d, $(objs))这段代码是将main.o sub.o变为.main.o.d .sub.o.d
  • test : $(objs) 这是直接使用objs对象进行编译
  • ifneq endif是Makefile的if条件编译语句
  • ifneq ($(dep_files),)表示如果dep_files为空,则条件为真,逗号后面有啥都没有表示空
  • include 语句和C语言的include语句类似,都是包含相应的头文件,使其编译
  • distclean标签用于清除.x.o.d文件

三、CFLAGS选项

CFLAGS 表示用于 C 编译器的选项,可以搭配各种选项达到不同的作用,现在我们介绍两种选项

1. -Werror 选项

该选项表示将所有警告当做错误进行处理,在程序开发时,建议采用这种方式,因为很多错误是由于警告引起来的,警告我们也不能忽视,如下为添加该选项后的Makefile文件代码


objs = main.o sub.odep_files := $(patsubst %, .%.d, $(objs))
dep_files := $(wildcard $(dep_files))CFLAGS = -Werrortest : $(objs)gcc -o test $^ifneq ($(dep_files),)
include $(dep_files)
endif%.o : %.cgcc -c -o $@ $< -MD -MF .$@.d
clean:rm *.o testdistclean:rm $(dep_files)
.PHONY: clean

2. -Idir 选项

-Idir选项可以指定编译器查找头文件的路径,加入这个选项后,我们用户定义的头文件就可以使用尖括号进行包含了,我们在根目录下新建include文件夹,并把所有的头文件放在这里,则Makefile就可以这么写了:


objs = main.o sub.odep_files := $(patsubst %, .%.d, $(objs))
dep_files := $(wildcard $(dep_files))CFLAGS = -Werror -Iincludetest : $(objs)gcc -o test $^ifneq ($(dep_files),)
include $(dep_files)
endif%.o : %.cgcc -c -o $@ $< -MD -MF .$@.d
clean:rm *.o testdistclean:rm $(dep_files)
.PHONY: clean

嵌入式Linux应用开发基础知识(六)——Makefile实例相关推荐

  1. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED驱动框架--面向对象、分层设计思想

    文章目录 前言 1.LED驱动程序框架 1.1.对于LED驱动,我们想要什么样的接口? 1.2.LED驱动要怎么写,才能支持多个板子?分层写 1.3.程序分析 驱动程序 应用程序 Makefile 1 ...

  2. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之第一个驱动

    文章目录 前言 1.Hello驱动 1.1.APP打开的文件在内核中如何表示? 1.2.打开字符设备节点时,内核中也有对应的struct file 1.3.如何编写驱动程序? 1.4.驱动程序代码 1 ...

  3. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之Pinctrl子系统和GPIO子系统的使用

    文章目录 前言 1.Pinctrl子系统 1.1.为什么有Pinctrl子系统 1.2.重要的概念 1.3.代码中怎么引用pinctrl 2.GPIO子系统 2.1.为什么有GPIO子系统 2.2.在 ...

  4. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架

    文章目录 前言 1.APP怎么读取按键值 1.1.查询方式 1.2.休眠-唤醒方式 1.3.poll方式 1.3.异步通知方式 1.5. 驱动程序提供能力,不提供策略 2.按键驱动程序框架--查询方式 ...

  5. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED模板驱动程序的改造:设备树

    文章目录 前言 1.驱动的三种编写方法 2.怎么使用设备树写驱动程序 2.1.设备树节点要与platform_driver能匹配 2.2.修改platform_driver的源码 3.实验和调试技巧 ...

  6. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之设备树模型

    文章目录 前言 1.设备树的作用 2.设备树的语法 2.1.设备树的逻辑图和dts文件.dtb文件 2.1.1.1Devicetree格式 1DTS文件的格式 node的格式 properties的格 ...

  7. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之总线设备驱动模型

    文章目录 前言 1.驱动编写的三种方法 1.1.传统写法 1.2.总线驱动模型 1.3.设备树驱动模型 2.Linux实现分离:Bus/Dev/Drv模型 2.1.Bus/Dev/Drv模型 2.2. ...

  8. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之驱动设计的思想:面向对象/分层/分离

    文章目录 前言 1.分离设计 驱动程序分析---程序分层 通用驱动程序---面向对象 个性化驱动程序---分离 APP 程序分析 前言 韦东山嵌入式Linux驱动开发基础知识学习笔记 文章中大多内容来 ...

  9. 【嵌入式Linux】嵌入式Linux应用开发基础知识之I2C应用编程和SMBus协议及AP3216C应用编程

    文章目录 前言 1.IIC协议和SMBUS协议 1.1.IIC协议 1.1.1.硬件框架 1.1.2.软件框架 1.1.3.读写数据格式 1.1.4.硬件结构--在硬件上是如何实现双向传输 1.2.S ...

最新文章

  1. 四川第七届 I Travel(bfs)
  2. 【C++】C++命名空间重定向
  3. 调整php-fpm,nginx调整php-fpm
  4. 大咖说:出道十五载,认知五迭代
  5. vue当前浏览器是否为ie_Vue进阶(六十八):JS-判断当前浏览器是否为IE
  6. 互斥量和信号量的区别
  7. Introduce Null Object(引入Null对象)
  8. as 运算符 与 where T : class
  9. 数据预处理第3讲:归一化与离散化
  10. 《python3网络爬虫开发实战》学习笔记:pyspider报错Exception: HTTP 599: SSL certificate problem...
  11. 电脑系统声音常见故障问题解决方法汇总
  12. Variable used in lambda expression should be final or effectively final
  13. 【Pytorch with fastai】第 11 章 :使用 fastai 的中级 API 进行数据处理
  14. HMAC_SHA1和SHA1的区别
  15. 观点动力学模型:主要理论与模型综述
  16. 【ct107d】蜂鸣器,继电器控制
  17. 关于高精度交流恒流源设计是怎样的?
  18. MacBook Pro外接显示器竖屏显示
  19. linux如何设置mac快捷键,Mac与Linux常用快捷键汇总
  20. pycharm远程调试多个项目出现(Permission denied)

热门文章

  1. 计算机老师的烦恼(二)
  2. 如何在局域网内搭建FTP服务器,实现信息共享
  3. mount查看linux分区大小,【128】Linux 中磁盘空间查询df、du和分区fdisk和挂载mount
  4. 鼠标滚动放大缩小图片
  5. oracle驱动jdbc接口,ORACLE中三种类型的JDBC驱动
  6. 离恨恰如春草,更行更远还生
  7. 期刊要求的 Conflict of Interest(利益冲突)怎么写? 模板来了!
  8. 导师谈话记录(9.3)
  9. 谁驱动公司?上帝赐予食物,魔鬼送来厨师
  10. 生活不止眼前的代码,还有书和远方!