为什么只在头文件中声明函数

5
我知道什么是头文件,但我仍然不明白为什么很多程序员会制作一个同名的头文件和源文件,并且只在头文件中放置函数的原型,而在源文件中说明函数的作用。
我从来没有把函数和它们的原型分别放在不同的文件中,而是把它们全部塞进头文件中。
问题是,为什么要为头文件创建一个源文件?它有什么优势吗?这只是为了让代码看起来更清晰吗?我不理解。

6
关键点在于实现文件是预编译的,因此人们只能看到你的头文件,这些文件不会泄露任何东西,而是提供了可用内容的摘要。 - chris
@chris:这可能是一个好的答案,而不是一个评论。 - robbrit
5个回答

11
如果您在头文件中实现一个函数,然后将该头文件包含到两个或多个不同的源文件中,就会出现同一个函数的多个定义。这违反了单一定义规则。
可以通过声明函数为内联来解决这个问题,但这可能会导致代码膨胀(除非链接器知道如何合并多个定义)。

只有在两个不同的源文件在不同的编译单元中时,这才是正确的。 如果您为所有源文件使用单个编译单元,则可以将定义放在任何地方,只要每个文件只在一个地方被#include'ed或者顶部有一个#pragma once。 这也会显着提高您的链接器时间,这通常是小/中型项目构建时间的80%左右(在MSVC中)。 缺点是所有内容都在一个大的全局范围内,因此您必须聪明地命名以避免冲突(或使用名称空间或封装)。 - Dan Bechard

7

通常在头文件中定义(不仅仅是声明)内联函数

对于非内联函数(例如函数体足够大的函数),您需要声明它们,然后在一个特定的编译单元中定义它们(即实现它们)。

然后,在链接时,链接器会解析适当的函数名称(通常是混淆名称)。您不希望有多个定义的函数。

只提供一个编译单元的函数定义可以使总构建时间稍微快一点。

使用链接时优化(例如在编译和链接期间都使用-flto选项的g++)会使事情变得更加复杂。

请注意,巨大的软件(某些可执行文件几乎有一千兆字节的二进制代码,仅链接它们就需要几分钟)会带来程序员无法想象的约束。只需尝试从源代码编译大型自由软件(Libreoffice、Firefox、Qt5等)即可猜测出问题。

顺便说一下,你原则上可以将某个程序的所有代码放在一个源文件中,但出于有效和明显的原因,人们不这样做。


是的,现代优化编译器可以内联函数,前提是在编译时已知其定义。 - Basile Starynkevitch
因为(除非您进行LTO),否则将无法进行内联。编译器将没有该函数的定义,也无法进行内联。 - Basile Starynkevitch
我不确定我曾经听说过一个占用一千兆字节的可执行文件。在WinXP32中,我看到的最大的可执行文件是64Mb(MRT.exe)。它似乎也是我开发电脑上最大的exe或dll文件,而且差距很大。 - Mooing Duck
1
@BasileStarynkevitch:那是错的,大多数链接器可以在跨越翻译单元时进行内联,使在头文件中进行内联几乎没有意义。 - Mooing Duck
这取决于你所称呼的链接器;使用 gcc -flto,实际的链接时间优化是由一个名为 lto1编译器完成的。 - Basile Starynkevitch
显示剩余3条评论

4
将函数定义放入头文件会导致使用任何非平凡系统时编译时间过长。对于小型项目,将所有内容设为inline可以工作,但对于大型系统(更不用说具有数亿行代码的大型系统)肯定行不通。

1
头文件中的函数声明提供了符号引用,可用于将代码链接在一起。当您编译单个源文件时,每个源文件都会生成为目标代码。
如果您想在另一个源文件中使用一个源文件的函数,则需要某种方式来知道该代码在哪里以及如何调用它。编译器能够使用函数声明正是出于这个原因。它知道如何调用它以及它返回什么,但它还不知道函数的源代码在哪里。
一旦所有源代码都编译成目标代码,链接器将把所有目标文件组装成可执行文件(或库),并解析这些符号引用。

0
如果您的所有代码都在一个单独的非共享文件中,那么它所处的位置并不重要,可以是头文件或源文件。
将代码拆分为头文件和源文件有两个主要原因。可以总结如下:
- 技术原因。如果您有几个相互交互的源文件,您需要包含头文件。如果您在头文件中定义了所有内容,那么您的代码将被多次定义 - 这将导致编译错误。 - 设计原因。头文件定义了软件的接口,可以将其分发给客户端软件,而无需暴露内部实现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接