翻译单元

简介

翻译单元(Translation Unit, 简称 TU) 是编译器进行独立编译的最小代码单元 —— 简单说,它是编译器处理代码时的 “基本工作单位”,最终会被编译成一个 .o(目标文件)

多个目标文件再通过链接器合并成可执行文件(如 .exe)或库文件(如 .a/.so)。

简单来说:翻译单元就是 一个.c文件+其递归包含的头文件(头文件中的头文件) 进行预处理后的代码文本

重复定义

关键特性: 翻译单元之间是 “独立的”

每个翻译单元的编译过程完全独立,编译器处理 a.c 时,完全不知道 b.c 的存在 !

这是理解重复定义问题的关键所在

举个直观例子:有 a.c 和 b.c 两个源文件,各自形成独立的翻译单元:

  • a.c 的翻译单元:a.c + 它包含的所有头文件(如 stdio.hutils.h);
  • b.c 的翻译单元:b.c + 它包含的所有头文件(可能和 a.c 有重叠,也可能不同);
  • 编译时,a.c 生成 a.ob.c 生成 b.o,两者互不干扰;
  • 只有到链接阶段,链接器才会把 a.ob.o 以及依赖的库文件(如 HAL 库的 .a 文件)合并成最终的可执行文件(如 stm32_app.elf)。

当多个翻译单元包含相同头文件时,可能会出现 “重复定义” 链接错误,当每个 .o 都有同名的全局实体定义(如全局变量、非内联函数等),链接时冲突

注意:

头文件保护宏只能防止 “同一翻译单元内的重复包含”,无法解决多个翻译单元的重复定义

如何避免重复定义

[!订单] 声明与定义

  • 声明(Declaration):告诉编译器 “有一个函数 / 变量存在”,但不提供具体实现或内存分配(相当于 “给个名字和类型,让编译器认识它”);
  • 定义(Definition):给函数 / 变量分配内存、提供具体实现(相当于 “真的创建这个东西,让它能被使用”);
  • 变量的赋值函数功能的实现属于 “定义” 的一部分

宏定义#define:宏的本质是 “文本替换”,全程不涉及 “编译 / 链接”,也不分配内存、不生成代码,所以在头文件进行宏定义,是不出现重复定义的。

声明与定义分离!!!

  1. 头文件中绝不能进行 无  static 的变量定义
  2. 头文件中绝对不能写 无  static 的函数实现

使用static关键字 ---(作用域隔离)

如果一定要在头文件中进行声明与定义,使用static关键字  关键字static 会把实体的 “链接属性” 从「外部链接」改成了「内部链接」 —— 每个 .c 文件(翻译单元)里的同名静态实体,会被编译器视为「完全独立的个体」,编译后只在自己的 .o 目标文件中可见,链接时不会互相干扰。  

[! 注意] 当要跨文件访问全局变量时,一般不会使用static在.h文件中定义变量 而是在.c文件定义后,在对应的.h文件中用extern声明该变量