驱动编写

1. Makefile的使用

Makefile基础 - Makefile教程 - 廖雪峰的官方网站

makefile 完美教程 - WittXie - 博客园

Makefile - 功能 - 菜鸟教程

规则的基本格式

Makefile 由若干条规则构成,每条规则的基本格式如下:

目标文件: 依赖文件1 依赖文件2 ...
	命令1
	命令2
	...
  • 目标文件:是要生成的文件或执行的操作的名称,可以是实际的文件名,也可以是伪目标(如 clean)。
  • 依赖文件:是生成目标文件所需的文件或其他目标的列表,如果依赖文件的修改时间晚于目标文件,则目标文件需要重新生成。
  • 命令:是用于生成目标文件的具体操作,必须以 Tab 键开头,可以是编译命令、复制命令等任意命令。
  • make执行时,默认执行第一条规则

伪目标

伪目标是不代表实际文件的目标,而是用于执行特定操作的标记。例如:

.PHONY: clean
clean:
	rm -f m.txt
	rm -f x.txt

在执行 make clean 时,clean 不会被视为文件名,而是直接执行其对应的命令。

伪目标的作用

  1. 避免与同名文件冲突
    • 如果当前目录下存在一个名为 clean 的文件,make clean 会认为 clean 目标已经是最新的,从而不会执行 clean 规则中的命令。
    • 通过将 clean 声明为伪目标,make 会忽略当前目录下是否存在名为 clean 的文件,直接执行 clean 规则中的命令。
  2. 提高执行效率
    • 当目标被声明为伪目标后,make 不会试图寻找该目标的隐含规则,从而提高执行效率。
  3. 确保规则执行
    • 伪目标确保每次执行 make clean 时,都会执行 clean 规则中的命令,而不会因为目标文件的存在而跳过。

执行多条命令

一个规则可以包含多条命令,但需要注意以下几点:

  • 每条命令必须以 Tab 键开头。
  • 如果希望多条命令在同一个 Shell 环境中执行,可以使用分号 ; 将命令连接起来,或者使用反斜杠 \ 将命令换行。
  • 使用#来注释

控制打印

默认情况下,make 会打印出它执行的每一条命令。如果不想打印某一条命令,可以在命令前加上 @,例如:

no_output:
	@echo 'not display'
	echo 'will display'

控制错误

make 在执行命令时,会检查每一条命令的返回值,如果返回错误(非 0 值),就会中断执行。如果希望忽略错误,继续执行后续命令,可以在需要忽略错误的命令前加上 -,例如:

ignore_error:
	-rm zzz.txt
	echo 'ok'

变量的使用

Makefile 支持使用变量,变量的定义和使用方式如下:

  • 定义变量变量名 = 变量值,例如 CC = gcc
  • 使用变量:使用 $(变量名)${变量名} 来引用变量的值,例如 $(CC)

依赖关系的自动推导

Makefile 可以根据文件的后缀名自动推导出一些常见的编译规则,例如:

  • 如果目标文件是 .o 文件,依赖文件是 .c 文件,则默认使用 $(CC) -c $< -o $@ 命令进行编译。
  • $< 是 Makefile 中的一个自动变量,表示依赖列表中的第一个依赖文件。
  • $@ 是 Makefile 中的一个自动变量,表示当前规则的目标文件。

文件名模式匹配

Makefile 支持使用通配符和模式匹配来简化规则的编写,例如:

  • 使用 %.o: %.c 可以匹配所有 .c 文件生成 .o 文件的规则。

  • make 的规则中,% 用于表示文件名的模式匹配,例如 %.o 可以匹配所有以 .o 结尾的文件。

包含其他 Makefile 文件

可以使用 include 指令来包含其他 Makefile 文件,例如:

include common.mk

这可以将 common.mk 文件中的规则和变量包含到当前 Makefile 中。

条件语句

Makefile 支持条件语句,可以根据条件来执行不同的规则或命令,例如:

ifeq ($(DEBUG), 1)
	CFLAGS = -g
else
	CFLAGS = -O2
endif

这可以根据变量 DEBUG 的值来设置不同的编译选项。

Makefile内置函数

在 Makefile 中,OFILES = $(CFILES:.c=.o) 是一个常用的语法,用于将变量 CFILES 中的每个 .c 文件名替换为对应的 .o 文件名。这种语法是 Makefile 特有的,称为替换引用

替换引用的语法

$(VAR:A=B)
  • VAR 是变量名。
  • A 是要被替换的字符串。
  • B 是替换后的字符串。

假设 CFILES 的值为:

CFILES = main.c utils.c

那么执行以下替换操作后:

OFILES = $(CFILES:.c=.o)

OFILES 的值将变为:

OFILES = main.o utils.o

替换引用是 patsubst 函数的一个简化形式。patsubst 函数的功能更强大,可以处理更复杂的模式匹配和替换。例如:

OFILES = $(patsubst %.c,%.o,$(CFILES))

这与 OFILES = $(CFILES:.c=.o) 的效果完全相同。

1. wildcard 函数

  • 功能:匹配文件名模式,并返回匹配的文件列表。
  • 语法$(wildcard pattern)
  • 示例
    CFILES = $(wildcard *.c)
    这会将当前目录下所有以 .c 结尾的文件名赋值给 CFILES

2. patsubst 函数

  • 功能:模式替换,将符合模式的字符串替换为指定的字符串。
  • 语法$(patsubst PATTERN,REPLACEMENT,TEXT)
  • 示例
    OFILES = $(patsubst %.c,%.o,$(CFILES))
    这会将 CFILES 中的每个 .c 文件名替换为 .o

3. addprefix 函数

  • 功能:给列表中的每个元素添加前缀。
  • 语法$(addprefix PREFIX,NAMES)
  • 示例
    SRC_FILES = file1.c file2.c
    ALL_FILES = $(addprefix src/,$(SRC_FILES))
    这会将 SRC_FILES 中的每个文件名添加前缀 src/

4. addsuffix 函数

  • 功能:给列表中的每个元素添加后缀。
  • 语法$(addsuffix SUFFIX,NAMES)
  • 示例
    FILES = file1 file2
    OBJ_FILES = $(addsuffix .o,$(FILES))
    这会将 FILES 中的每个文件名添加后缀 .o

5. basename 函数

  • 功能:去掉文件名中的后缀。
  • 语法$(basename NAMES)
  • 示例
    OBJS = ./build/main.o ./build/func.o
    BASE_OBJS = $(basename $(OBJS))
    这会将 OBJS 中的每个文件名去掉后缀,结果为 ./build/main ./build/func

6. dir 函数

  • 功能:获取文件名中的目录部分。
  • 语法$(dir NAMES)
  • 示例
    OBJS = ./build/main.o ./build/func.o
    DIR_OBJS = $(dir $(OBJS))
    这会将 OBJS 中的每个文件名的目录部分提取出来,结果为 ./build/ ./build/

7. notdir 函数

  • 功能:获取文件名中的非目录部分。
  • 语法$(notdir NAMES)
  • 示例
    OBJS = ./build/main.o ./build/func.o
    NOTDIR_OBJS = $(notdir $(OBJS))
    这会将 OBJS 中的每个文件名的非目录部分提取出来,结果为 main.o func.o

8. shell 函数

  • 功能:执行 Shell 命令,并返回其输出结果。
  • 语法$(shell command)
  • 示例
    CURRENT_TIME := $(shell date)
    这会将 date 命令的输出结果赋值给 CURRENT_TIME

作业

  1. 解释CFILES = $(wildcard *.c )是什么意思?

回答:

wildcard 函数wildcard 是 Makefile 中的一个内置函数,用于匹配符合特定模式的文件名。它的语法是 $(wildcard pattern),其中 pattern 是一个文件名模式,可以包含通配符 *?

所以这行的意思是将当前目录下所有的.c文件名列表赋值给变量CFILES

  1. 解释下列语句
.c.o :  
	$(CC) -c $<

在 Makefile 中,.c.o 是一个模式规则,用于定义如何从 .c 文件生成 .o 文件。具体来说,.c.o 规则告诉 make 如何将一个 .c 文件编译成对应的 .o 文件。

  • **.c.o**:这是一个模式规则,表示从 .c 文件生成 .o 文件。
  • **$(CC)**:这是编译器变量,默认值通常是 gccg++
  • **-c**:这是编译器选项,表示只编译源文件,生成目标文件,但不进行链接。
  • **$<**:这是自动变量,表示依赖列表中的第一个依赖文件。在这个规则中,$< 表示 .c 文件。
  • **$@**:这是自动变量,表示目标文件。在这个规则中,$@ 表示 .o 文件。

2.库文件的区别及用途

.o 文件(目标文件)

  • 定义:.o 文件是编译器将源代码文件(如 .c.cpp)编译后生成的目标文件。它包含了编译后的机器代码和符号信息,但尚未被链接成可执行文件。
  • 用途:.o 文件是编译过程中的中间产物,通常用于多文件项目。在链接阶段,多个 .o 文件会被链接器组合成一个可执行文件。
  • 生成方法
    gcc -c hello.c -o hello.o
    这条命令将 hello.c 编译成 hello.o 文件。

.a 文件(静态库)

  • 定义:.a 文件是静态链接库文件,也称为归档文件。它是一组目标文件(.o 文件)的集合,通常包含多个函数和变量的实现。
  • 用途:静态库在程序编译时被链接到目标程序中,程序运行时不再需要该静态库。它适用于需要将库代码直接嵌入到可执行文件中的场景。
  • 生成方法
    ar rcs libmyhello.a hello.o
    这条命令将 hello.o 文件打包成静态库 libmyhello.a
  • 使用方法
    gcc -o hello main.c -L. -lmyhello
    这条命令将 main.c 与静态库 libmyhello.a 链接,生成可执行文件 hello

.so 文件(共享库)

  • 定义:.so 文件是共享对象文件(Shared Object),也称为动态链接库。它是一种在程序运行时动态加载的库文件,可以被多个程序共享使用。
  • 用途:共享库在程序运行时被动态加载,减少了程序的体积,提高了代码的复用性。它适用于需要在运行时动态加载和更新库的场景。
  • 生成方法
    gcc -shared -fPIC -o libmyhello.so hello.o
    这条命令将 hello.o 文件编译成共享库 libmyhello.so
  • 使用方法
    gcc -o hello main.c -L. -lmyhello
    这条命令将 main.c 与共享库 libmyhello.so 链接,生成可执行文件 hello。运行时,需要确保共享库在系统的库路径中,或者通过设置 LD_LIBRARY_PATH 环境变量来指定库路径。

总结

  • .o 文件:编译后的目标文件,用于链接阶段。
  • .a 文件:静态链接库,编译时链接到目标程序中。
  • .so 文件:共享库,运行时动态加载。