为什么我们不应该在C中包含源文件

为什么将源文件包含在其他源文件中并不是一种好的做法? 更好的方法是包含头文件。 这种方法有什么好处,反之亦然? 请原谅我糟糕的英语。

为什么将源文件包含在其他源文件中并不是一种好的做法?

源文件包含定义。 这些可能导致多个定义错误 ,因此通常不应包含在其他源文件中。 即使通过仅编译包含其他源文件的文件来避免多个定义错误,代码也可能变得难以管理。

在头文件中,您只需向编译器引入一些符号并通知其类型。 这允许您将接口与实现分开。

例如:

档案交流

 int a = 42; ... 

文件bc

 /* Example of bad code */ #include "ac" ... 

当您编译acbc并链接它们时,您将收到multiple definition链接器错误。

如果计划将多个源文件包含在一个文件中并编译该文件,则会引入大量污染(宏,静态函数等),这对于读者和编译器来说是不易管理的。


ps当我说一般时,我的意思是有时候包括源代码可能会有用。 但在这种情况下,为了避免与读者的混淆,我宁愿将文件后缀重命名为.c之外的某些东西,可能是.inc或类似的。

编译任何文件#include d,就好像它的文本确实替换处理器的相应#include指令一样。 虽然不是很合适,但在这里您可以找到有关此内容的更多信息。 注意预处理器保护。

实际的问题是,你不应该把这个标题放进去。 这将是使得具有外部链接的名称在多个编译单元 /模块中定义的所有 思考 。 您也不应该在此处放置对象,这些对象仅在一个此类模块中使用,和/或应对其他模块隐藏。

这将包括一般的function:标题仅提供声明定义将在单个模块中。 inline函数是一个例外,它实际上必须在头文件中定义。

对于数据结构,大多数情况下,同样适用于函数。 但是, static结构可能存在例外情况,必须由所有模块提供。 另一个例外可能是自动生成的文件,例如仅由单个模块使用的表。 这些也应该是#include d,但是声明为static 。 通常,人们会对这些文件使用不同的扩展名,例如.inc而不是.h

对于预处理器,文件的扩展名无关紧要。 您可以将代码放入带有“JPG”扩展名的文件中,如果代码是合法的,您仍然可以#include它而不会出错。

传统上, #include包含源文件扩展名的文件被认为是不好的做法的原因之一是从基本的构建/制作角度来看。 想象一下,您正在将一个大型项目移植到一个新的跨平台构建系统(例如,5000万行代码)。

您现在必须指定要将哪些文件构建为单独编译单元(目标文件),以便单独编译并链接以形成生成的二进制文件。 如果您的代码库习惯使用预处理器来包含具有源文件扩展名的文件,那么您根本不知道只查看文件扩展名哪些文件将作为单独的编译单元构建,哪些文件实际上只是包含在内由预处理器。 那么你可能会面临一个错误的垃圾邮件,只是试图将所有源文件构建为单独的编译单元作为一个理智的人,并且可能必须使用细齿梳调试您的构建过程,同时检查所有代码并试图计算哪个文件适合什么。

在更高级别,超出文件扩展名,如果您实际在源文件中定义事物并将它们包含在预处理器中,那么您将面临相同符号的冗余链接器定义,即棘手的链接时(以及可能的编译时)错误。 此外,这可能表现出在界面/声明(标题)与实现/定义(源)分离之间的思维方面的总体崩溃。

有一些例外,例如统一构建,它可以作为构建时优化,并且可以通过仔细的编码标准和实际的实际测量益处进行一些接受,但一般来说,包括源文件可能真的令人困惑,并且表明开发人员并不真正理解将声明与定义分离或者在尝试建立构建系统时可能导致的混淆。

如前所述,反对将C文件包含到C文件中的主要论点是多重定义错误的高风险。 由于它是一种很少使用的技术,它会给代码维护者带来意想不到的副作用。

当然,在非常特殊的情况下,包括C-File可能是两个邪恶中的较小者。 例如,如果要为静态c函数编写unit testing,可以使用unit testing将C文件包含到文件中。

另请参见: 如何测试静态函数

另一个不寻常但有效的用法是将类或函数模板与其定义(C ++)分离: https : //isocpp.org/wiki/faq/templates#separate-template-fn-defn-from-decl

c源文件必须有定义,例如,如果你有一个添加两个数字的函数int add(int, int) ,那么它的定义看起来像

 int add(int x, int y) { return x + y; } 

头文件,包含一个原型 ,帮助编译器在代码中调用它时调用此函数,它告诉编译器如何为函数创建堆栈帧,有多少参数及其类型,以及返回类型。

如果你包含来自上面的代码示例的ac源文件,那么将需要函数add()两个定义 ,这是不可能的。

而是将原型添加到头文件中,就像这样

 int add(int x, int y); 

然后包含头文件,这样add()函数将有一个单一的定义。

您可能会问自己,如果我在另一个c源文件中使用它而不提供定义,该函数将如何工作?

答案是只有当编译器将所有目标文件链接到最终二进制文件时才需要函数定义。