嵌入式开发学习日志(多文件工程、Makefile) day17 持续更新中

嵌入式开发学习日志(多文件工程、Makefile) day17 持续更新中

前言

在小型 C 语言项目中,代码量少可以全部写在一个.c文件里。当项目规模变大,全部代码写在单个源文件会造成维护困难、可读性差、不利于团队协作。这时就要使用多文件工程管理,配合Makefile工具实现项目自动化编译。本文梳理头文件、源文件分工、全局变量跨文件使用、Makefile 语法与变量相关知识点。


一、C 语言多文件工程结构

C 工程主要分为 \\头文件.h源文件.c\\两类文件。

1. 头文件(.h)职责

头文件只做声明,不做实现

  1. 包含需要的其他头文件

  2. 数据类型声明(结构体、枚举、typedef 等)

  3. 函数声明

  4. 全局变量声明(使用extern

  5. 宏定义

⚠️注意:头文件中不要定义函数实体、不要初始化全局变量,否则多次 include 会出现重复定义报错。

2. 源文件(.c)职责

源文件做定义与实现

  1. 包含需要的头文件(一定要包含自身对应的.h头文件)

  2. 函数的具体实现(函数体)

  3. 全局变量的定义(可以初始化)

工程一般是一对多关系:一个.h头文件,对应一个.c实现文件;多个模块就有多组 h‑c 文件。main.c 作为程序入口,包含各个模块头文件,调用模块函数。


二、全局变量跨文件使用规则

全局变量在多文件项目中极易出现重复定义错误,严格遵守下面 4 条规则:

  1. 全局变量在源文件.c中定义(可以初始化)

  2. 如果只在定义该变量的.c内部使用,不需要额外声明。

  3. 如果要在其他外部源文件使用这个全局变量,必须做外部声明。

  4. 在对应头文件中使用extern关键字做变量声明;extern 声明时,不能给变量初始化

示例:

// test.c 源文件:定义全局变量 int g_num = 100; // test.h头文件:extern声明,供别的.c文件使用 extern int g_num;

其他文件只要#include "test.h"就可以访问g_num

❌错误:头文件写extern int g_num=100;,这变成定义,会重复定义。


三、Makefile 工程编译管理

3.1 Makefile 是什么

Makefile 用于管理代码工程的编译、链接,由make工具解释执行,实现一键编译链接整个工程,不用手动敲很长 gcc 命令。

文件名可以为Makefile或者makefile

3.2 Makefile 基础语法规则

目标文件: 依赖文件 编译规则命令 #❗前面必须是Tab键,不能用空格
  • 目标文件:想要生成的文件,比如可执行程序a.out

  • 依赖文件:生成目标所需要的文件,一般是.c源码

  • 编译规则:gcc 编译命令,生成目标。

示例:

a.out: main.c sum.c sub.c div.c gcc main.c sub.c sum.c div.c -I头文件路径 -L库的位置

编译运行,终端直接输入命令:

make
gcc 参数说明
  • -I(大写 i):指定头文件搜索路径

  • -L:指定库文件搜索路径


四、Makefile 中的变量

变量可以简化 Makefile,便于后期维护,修改只需要改变量,不用修改多处编译命令。

4.1 自定义变量

定义格式:变量名 = 值,引用格式:$(变量名)

#定义变量 OBJ = a.out SRC = main.c sum.c sub.c #引用变量 $(OBJ) 等价于 a.out

变量赋值运算符:

运算符

作用

=

普通赋值,给变量赋值

+=

追加赋值,在原有变量基础上新增内容

?=

条件赋值:变量未赋值才赋值;已有值则保留原值

示例:

SRC = main.c SRC += sum.c # SRC变为 main.c sum.c

4.2 Makefile 内置系统自动变量(直接使用)

自动变量

含义

$@

代表当前规则的目标文件

$^

代表所有的依赖文件

$<

代表第一个依赖文件

4.3 完整 Makefile 示例

#自定义变量 OBJ = a.out SRC = main.c SRC += sum.c SRC += sub.c SRC += div.c CC = gcc INCLUDE = ../include LIBS = ../lib #编译规则 $(OBJ): $(SRC) $(CC) $^ -o $@ -I$(INCLUDE) -L$(LIBS) -lm #伪目标:清理编译产物 clean: rm $(OBJ)

使用:

  • make:执行编译,生成 a.out

  • make clean:执行 clean 规则,删除可执行文件

补充:clean没有依赖,属于伪目标,一般用来删除编译生成的产物。


五、常见踩坑总结

  1. ❌头文件写函数实现、初始化全局变量,多次#include触发重复定义报错。 ✅头文件放声明;.c文件放实现与变量定义。

  2. extern int g_val = 10;头文件 extern 声明带初始化。 ✅extern 只是声明,不能初始化;定义写在.c

  3. ❌Makefile 编译命令前面用空格,不是 Tab,直接报语法错误。 ✅规则行命令开头必须是 Tab。

  4. ❌忘记-I指定头文件路径,头文件找不到报错。

  5. ❌手动敲很长 gcc 命令,文件一多修改麻烦。 ✅用 Makefile + 变量统一管理。


小结

  1. .h负责声明,.c负责实现;多文件工程遵循声明与实现分离。

  2. 全局变量在.c定义,.h中用 extern 做外部声明,禁止 extern 时初始化。

  3. Makefile 格式:目标:依赖,Tab 开头写编译命令,make 命令执行。

  4. 使用自定义变量简化维护,掌握$@ $^ $<自动变量,简化编译脚本。