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

为什么我们不应该在C中包含源文件,c,code-organization,C,Code Organization,为什么将源文件包含到其他源文件中不是一个好的做法? 更好的方法是包含头文件。这种方法的好处是什么?反之亦然,缺点是什么? 请原谅我英语不好 为什么将源文件包含到其他源文件中不是一个好的做法 档案 源文件包含定义。这些可能导致多个定义错误,因此通常不应包含在其他源文件中。即使通过只编译包含其他源文件的文件来避免多定义错误,代码也可能变得不可管理 在头文件中,您只需向编译器引入一些符号并通知它们的类型。这允许您将接口与实现分离 例如: 文件a.c int a = 42; ... /* Exampl

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

为什么将源文件包含到其他源文件中不是一个好的做法 档案

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

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

例如:

文件a.c

int a = 42;
...
/* Example of bad code */
#include "a.c"
...
文件b.c

int a = 42;
...
/* Example of bad code */
#include "a.c"
...
当您编译
a.c
b.c
并链接它们时,您将得到
多个定义
链接器错误

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



p、 当我说一般的时候,我的意思是有时包括源代码可能是有用的。但在这种情况下,为了避免读者混淆,我更愿意将文件后缀重命名为
.c
以外的其他名称,可以是
.inc
或类似名称。

c源文件必须有定义,例如,如果您有一个添加两个数字的函数
int add(int,int)
,那么它的定义应该是

int add(int x, int y)
 {
    return x + y;
 }
头文件包含一个原型,它帮助编译器在代码中调用此函数时调用此函数,它告诉编译器如何为函数创建堆栈框架、有多少个参数及其类型以及返回类型

如果包含包含上述代码示例的c源文件,则需要函数
add()
的两个定义,这是不可能的

而是将原型添加到头文件中,如下所示

int add(int x, int y);
然后包括头文件,这样就为
add()
函数提供了一个单一的定义

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


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

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

当然,在非常特殊的情况下,包括C文件可能是两个缺点中较小的一个。例如,如果您想为静态c函数编写单元测试,您可以将c文件包含到包含单元测试的文件中

另见:

另一个不寻常但有效的用法是将类或函数模板与其定义(C++)分离:

编译任何文件
#include
d时,其文本将取代预处理器的相应
#include
指令。虽然不完全合适,但在这里您可能会找到关于这一点的更多信息。请注意预处理器防护装置

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

这通常包括函数:头只提供声明,定义将在单个模块中。一个例外是
内联
函数,它们实际上必须在标题中定义


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

对于预处理器,文件的扩展名实际上并不重要。您可以将代码放入一个扩展名为“JPG”的文件中,只要代码是合法的,您仍然可以无误地包含它

传统上,将带有源文件扩展名的文件包括在内被认为是不好的做法的原因之一是从基本的构建/制作角度出发的。假设您正在将一个大型项目移植到一个新的跨平台构建系统(例如,5000万行代码)

现在,您必须指定哪些文件将构建为单独的编译单元(对象文件),以单独编译并链接以形成结果二进制文件。如果您的代码库习惯于使用预处理器来包含具有源文件扩展名的文件,那么您不知道只查看文件扩展名,哪些文件将构建为单独的编译单元,哪些文件实际上将由预处理器包含。因此,如果你像一个理智的人那样试图将所有源文件作为单独的编译单元构建,那么你可能会面临大量的错误,并且在检查所有代码并试图找出哪个文件是用于什么目的时,你可能不得不使用细牙梳来调试构建过程

在更高的级别上,除了文件扩展名之外,如果您在源文件中实际定义了内容并将其包含在预处理器中,那么您就有可能会出现t的冗余链接器定义