翻译单元
1. 基本定义
预处理完成后得到的源文件称为一个翻译单元 (Translation Unit)。它由外部声明序列组成,例如函数定义、对象定义、typedef 声明和 extern 声明。编译器先分别处理各翻译单元生成目标文件,再由链接器把目标文件与库连接为最终程序。
2. 为什么它是模块边界
同名标识符能否共存、某个符号能否被其他源文件访问,都取决于翻译单元边界和链接属性。static 影响可见性,头文件影响声明一致性,链接器负责把调用点与定义点绑定。理解翻译单元,才能正确理解“声明写在哪”“定义放在哪”。
3. 一个最小示例
/* math_ext.h */
#ifndef MATH_EXT_H
#define MATH_EXT_H
int add(int lhs, int rhs);
#endif2
3
4
5
6
7
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
/* math_ext.c */
#include "math_ext.h"
int add(int lhs, int rhs) {
return lhs + rhs;
}2
3
4
5
6
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
/* main.c */
#include <stdio.h>
#include "math_ext.h"
int main(void) {
printf("%d\n", add(3, 4));
return 0;
}2
3
4
5
6
7
8
可能的输出(示例):
<输出与输入或平台相关,请以实际运行为准>
main.c 和 math_ext.c 是两个翻译单元。它们都包含同一个头文件,从而共享一致声明。链接阶段再把 add 的调用点和定义点连起来。
4. 头文件重复包含
头文件会被预处理器按文本插入,同一头文件可能被多次包含。如果缺少包含保护,重复声明和重复定义会直接破坏翻译单元语义。包含保护(或 #pragma once)不是风格问题,而是构建正确性问题。
5. 声明一致性约束
多个翻译单元若都引用同一个外部符号,就必须看到彼此一致的声明。若某个源文件里声明写错了类型,即使能编译通过,也可能在链接后产生难以定位的运行错误。把公共声明集中在头文件,并确保所有调用点都只通过头文件获取声明,是避免这类问题的基础做法。
6. 内部符号收敛
不需要跨翻译单元暴露的函数和对象,建议使用 static 限定在当前翻译单元。这样不仅能减少符号污染,也能让链接阶段更容易发现真正的接口边界。
7. 暂定定义与链接结果
文件作用域下形如 int g; 的声明在没有初始化器时可能形成暂定定义。单个翻译单元内可以出现多次同名暂定定义,最终会合并为一个定义;但跨翻译单元若都给出外部定义,就会在链接阶段产生多重定义冲突。把“只声明放头文件、唯一定义放实现文件”这条规则落实好,通常就能避免此类问题。