在另一个C源文件中包含一个C源文件?

150

在另一个.c文件中#include一个.c文件是否可以(甚至建议/好的实践)?

11个回答

143

如果使用得当,这可以是一种有用的技术。

假设您有一个复杂的、性能关键的子系统,具有相当小的公共接口和大量不可重用的实现代码。代码运行到几千行,有一百多个私有函数和相当多的私有数据。如果您使用非平凡的嵌入式系统,您可能经常处理这种情况。

您的解决方案可能是分层、模块化和解耦的,这些方面可以通过在不同的文件中编码子系统的不同部分来有用地表示和强化。

使用 C 时,这样做可能会失去很多。几乎所有的工具链都为单个编译单元提供了良好的优化,但对于任何声明为 extern 的内容都非常悲观。

如果将所有内容放入一个 C 源模块中,则会获得以下优点:

  • 性能和代码大小的改进 - 在许多情况下,函数调用将被内联。即使没有内联,编译器也有机会生成更有效的代码。

  • 链接级别的数据和函数隐藏。

  • 避免命名空间污染及其必然结果 - 您可以使用较少笨重的名称。

  • 更快的编译和链接。

但是,当涉及到编辑此文件并且失去了隐含的模块化时,您也会得到一个混乱不堪的结果。这可以通过将源代码拆分为多个文件并包含这些文件以生成单个编译单元来克服。

但是,您需要强制执行一些约定以正确管理此过程。这些约定在某种程度上取决于您的工具链,但一些常见的指针是 -

  • 将公共接口放在单独的头文件中 - 无论如何,您都应该这样做。

  • 有一个主要的.c文件,其中包括所有子文件。这也可以包括公共接口的代码。

  • 使用编译器保护程序确保私有头文件和源模块不被外部编译单元包含。

  • 所有私有数据和函数都应声明为静态。

  • 保持.c和.h文件之间的概念区别。这利用了现有的约定。区别在于您的头文件中将有很多静态声明。

  • 如果您的工具链没有任何理由不这样做,请将私有实现文件命名为.c和.h。如果您使用包含保护程序,这些文件将不会产生代码并且不会引入新名称(您可能会在链接期间得到一些空段)。巨大的优势是其他工具(例如IDE)将适当地处理这些文件。


3
+1 这仍然是现实,但更好的编译器将使这种方法随着时间的推移过时。使用链接时优化的GCC 4.5是迈向成功的重要一步。 - u0b34a0f6ae
3
我见过很多程序都这样做,当我试图重用它们的代码时,这让我很恼火。谢谢你解释为什么他们这样做,并建议使用一个保护条件(通常不被执行)。 - Joey Adams
2
在C51嵌入式开发中,使用extern和多个C文件只会带来麻烦。我正在转向将一个C文件包含所有其他文件,以节省时间。 - mahesh
1
记录一下:GCC支持跨不同翻译单元的优化,详见此SO线程以及GCC手册中关于链接时优化的章节。 - Twonky
1
还有一点需要说明的是:链接时优化本质上更难,除非你能够以某种方式保留与代码相关联的C源文件中所有信息,使编译器可以从中获取到所有信息(这基本上就是上面链接所描述的GCC所做的)。为了让其他开发人员从您分布式的目标文件中受益,LTO需要更大的目标文件才能实现相同的优化。根据LTO的实现方式,它可能无法实现单个翻译单元内优化的100%。LTO也可能会使构建变慢。 - mtraceur
显示剩余2条评论

78

这会编译吗?可以,它能够成功编译。

这是否推荐?不推荐 - .c文件会被编译成.obj文件,然后由链接器将它们在编译后链接在一起形成可执行文件(或库),所以没有必要在一个.c文件中包含另一个.c文件。相反,您可能想做的是制作一个.h文件,列出其他.c文件中可用的函数/变量,并将该.h文件包含进去。


20
值得注意的是,即使代码编译通过,如果 #include 的 .c 文件也被编译并且两个目标文件被链接在一起,仍然可能无法链接成功--你可能会遇到重复定义符号的问题。 - Nick Meyer
1
一个小问题。我有一个头文件声明了一个结构体和方法,还有相应的.c文件来定义它们。如果该方法将结构体作为参数传递,那么我该如何避免在另一个定义主方法的.c文件中包含该.c文件? - stdout

17

不行。

根据您使用的构建环境(您没有具体说明),您可能会发现它按照您想要的方式正常工作。

然而,有许多环境(包括IDE和大量手工制作的Makefile)期望编译 *.c 文件-如果发生这种情况,由于重复符号,您可能最终会遇到链接器错误。

通常情况下应该避免这种做法。

如果您绝对必须 #include 源代码文件(一般来说应该避免),请使用不同的文件后缀名。


10

您可以使用Linux中的gcc编译器将两个c文件链接成一个输出。假设你有两个c文件,一个是'main.c',另一个是'support.c'。因此,连接这两个文件的命令为:

gcc main.c support.c -o main.out

通过这两个文件,将链接到一个单独的输出文件 main.out。 运行输出文件的命令为:

./main.out

如果您在main.c中使用了support.c文件中声明的函数,则还应在main中使用extern存储类来声明它。


10

我想分享一个团队决定包含.c文件的情况。我们的架构主要由通过消息系统解耦的模块组成。这些消息处理程序是公开的,并调用许多本地静态工作函数来完成它们的工作。问题出现在尝试为我们的单元测试用例获取覆盖率时,因为唯一间接执行此私有实现代码的方式是通过公共消息接口。由于某些工作函数深入堆栈,因此达到适当覆盖率这转变成了一场噩梦。

包含.c文件给了我们一种方法来达到我们感兴趣的测试中机器关键部分。


5
文件的扩展名对大多数C编译器并不重要,因此它可以正常工作。
但是,根据您的makefile或项目设置,所包含的c文件可能会生成单独的目标文件。在链接时,这可能会导致符号重复定义。

3
您可以将.C或.CPP文件正确地包含到其他源文件中。 根据您的IDE,通常可以通过查看要包含的源文件属性(通常右键单击并单击属性),取消选中/选中编译/链接/从构建中排除或任何可能的选项来防止双重链接。或者您可以不将文件包含在项目本身中,这样IDE甚至不知道它的存在,并且不会尝试编译它。对于makefiles,您只需不将文件放入其中进行编译和链接即可。

2
将C文件包含在另一个文件中是合法的,但不建议这样做,除非您确切知道为什么要这样做以及您想要实现什么目标。如果您在问题后面提供原因,社区几乎肯定会为您找到更合适的方法来实现您的目标(请注意,“几乎”这个词,因为在特定情况下这可能是解决方案)。
顺便说一下,我错过了问题的第二部分。如果将C文件包含在另一个文件中,并同时将其包含到项目中,则可能会出现重复符号问题,即在链接对象时同一函数将被定义两次(除非它们全部都是静态的)。

1
许多其他答案已经覆盖了如何做到这一点,但在正常情况下你为什么不应该这样做。话虽如此,我将解释为什么我过去这样做。
在嵌入式开发中,将芯片供应商源代码作为编译文件的一部分很常见。问题是这些供应商可能没有与您的组织相同的样式指南或标准警告/错误标志设置。
因此,您可以创建一个包含供应商源代码的本地源文件,然后编译此包装器C文件以抑制所包含的源中的任何问题以及由该源包含的任何头文件。例如:
/**
 * @file   vendor_wrap.c
 * @brief  vendor source code wrapper to prevent warnings
 */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnested-externs"
#include "vendor_source_code.c"
#pragma GCC diagnostic pop

这样做可以让您使用更简单的Make脚本,具有一组标准的编译器标志和设置,而不是在脚本中为某些文件定制标志。在代码中可以有特定的例外情况。
gcc main.c vendor_wrap.c -o $(CFLAGS) main.out

0

C语言并不禁止那种#include,但是生成的翻译单元仍然必须是有效的C。

我不知道你正在使用什么带有.prj文件的程序。如果你正在使用像“make”或Visual Studio之类的东西,只需确保将其要编译的文件列表设置为不包含无法独立编译的文件即可。


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