起源:为什么要有头文件

头文件在 1970 年原始的 C 中并不存在。代码的复用靠的是编译后的链接过程. 这也是当时语言的主流选择.

如此的一个最重要的原因是当时的程序设计哲学讲求二进制复用以兼容不同高级语言的编译产物. 因此,C 程序里可能存在对其它语言函数的调用。比起其它 C 程序, 这些函数对于 C 编译器来说更是一头糨糊. 编译的时候碰到这些函数只会识别为外部符号等待链接。既然如此, 没有任何必要为 C 程序单独处理,这也符合 C 语言尽可能简单易实现的哲学.

如此,函数声明也是不必要的.

事实上,现代的 C 程序依然保持这这种兼容性. 例如以下代码完全可以通过编译.

int main(){
puts("Hello");
}

这里的 puts 就需要连接 stdio.h 才能正常调用.

那么一个很重要的问题就是,各个编程语言的数据类型风马牛不相及, 链接的时候怎么保证参数和返回值类型检查呢?

在当时根本不需要考虑这个问题。因为 当时的语言基本上都只有字长一种数据类型.

没错,C 语言的类型系统是后加的。因此为了兼容性,C 语言是弱类型的.

这类编程语言的函数调用 f(a, b, c) 就是简单的按 c, b, a 的顺序把参数入栈然后跳转。虽然对于函数这个被调用者栈长度不可知, 但是栈顶有什么是确定的 (就是参数). 而函数执行完成后把返回值放进数据寄存器返回后. 调用者是知道栈的全部信息的,因此可以负责维护好栈.

当然,这个方法的弊端就是函数没法验证栈顶值的有效性, 调用者也不知道返回值的有效性. 因此要是不按约定乱传参返回就会出现很隐蔽的 bug.

后来数据类型被加上了, 参数和返回值的长度变成了变量.(后来 C++ 的重载和模板让编译器连调用哪个函数都要抓瞎.) 事情开始麻烦了.

为了让调用者知道这些信息以便维护栈,函数声明就应运而生. 为了方便在不同的文件的声明同一批函数,头文件也就诞生啦.

至于为何设计的如此简陋,那就是 C 语言哲学的问题了.

例外

inline 函数

const 对象只要一出文件就不可见.

引用

为什么 C/C++ 要分为头文件和源文件? wzsayiie 的回答