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

Posted

技术标签:

【中文标题】在另一个中包含一个 C 源文件?【英文标题】:Including one C source file in another? 【发布时间】:2010-09-18 23:05:42 【问题描述】:

#include 一个.c 文件放在另一个.c 文件中是否可以(甚至推荐/良好做法)?

【问题讨论】:

【参考方案1】:

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

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

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

使用 C,您可能会因此而失去很多。几乎所有工具链都为单个编译单元提供了不错的优化,但对任何声明为 extern 的东西都非常悲观。

如果你把所有东西都放在一个 C 源模块中,你会得到 -

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

链接级数据和功能隐藏。

避免命名空间污染及其必然结果 - 您可以使用不那么笨拙的名称。

更快的编译和链接。

但是,在编辑此文件时,您也会遇到一团糟,并且您会失去隐含的模块化。这可以通过将源代码拆分为多个文件并包含这些文件以生成单个编译单元来克服。

您需要强加一些约定来正确管理它。这些在某种程度上取决于您的工具链,但一些通用指针是 -

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

拥有一个包含所有附属 .c 文件的主 .c 文件。这也可能包括公共接口的代码。

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

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

保持 .c 和 .h 文件之间的概念区别。这利用了现有的约定。不同之处在于您的标题中会有很多静态声明。

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

【讨论】:

+1 这仍然是现实,而更好的编译器会使这种方法随着时间的推移而过时。具有链接时间优化的 GCC 4.5 是向前迈出的一大步。 我见过很多程序这样做,当我试图重用它们的代码时,这让我很紧张。感谢您解释他们为什么这样做,并建议使用警卫(通常不这样做)。 在 C51 的嵌入式开发中,使用 extern 和多个 C 文件只会让人头疼。我正在切换到让一个 C 文件包含所有其他文件以恢复我的时间。 记录:GCC 支持跨不同翻译单元的优化,请参阅this SO thread 和link time optimization 上的 GCC 手册部分。 另外记录一下:链接时优化本质上更难,除非您可以以某种方式保留与正在链接的目标文件中的代码相关联的 C 源文件中的所有信息对编译器可用(即基本上上面的链接描述了 GCC 所做的事情)。为了让其他开发人员从您的分布式目标文件中受益,LTO 需要更大的目标文件来实现相同的优化。根据 LTO 的实施方式,它可能无法实现在单个翻译单元内进行优化所能达到的 100% 的优化。 LTO 还可以使构建变慢。【参考方案2】:

还好吗?是的,它会编译

推荐吗? no - .c 文件编译为 .obj 文件,这些文件在编译(通过链接器)到可​​执行文件(或库)后链接在一起,因此无需在另一个中包含一个 .c 文件。您可能想要做的是创建一个 .h 文件,其中列出了另一个 .c 文件中可用的函数/变量,并包含 .h 文件

【讨论】:

另外值得注意的是,即使它编译,如果#included .c 文件也被编译并且两个目标文件链接在一起,它可能不会链接——你最终可能会得到多重定义的符号。 一个小问题。我有一个头文件,它声明了一个 struct + 方法以及相应的 .c 文件来定义它们。如果该方法将结构作为参数,我将如何避免将 .c 文件包含在另一个定义了 main 方法的 .c 文件中?【参考方案3】:

没有。

根据您的构建环境(您未指定),您可能会发现它完全按照您想要的方式工作。

但是,有许多环境(IDE 和大量手工制作的 Makefile)需要编译 *.c - 如果发生这种情况,您可能会因重复符号而导致链接器错误。

通常应避免这种做法。

如果您绝对必须#include source(通常应该避免),请为文件使用不同的文件后缀。

【讨论】:

【参考方案4】:

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

包含 .c 文件使我们能够找到我们感兴趣的测试机器中的齿轮。

【讨论】:

【参考方案5】:

您可以使用 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 中也使用外部存储类声明它。

【讨论】:

【参考方案6】:

文件的扩展名对大多数 C 编译器来说并不重要,所以它会起作用。

但是,根据您的 makefile 或项目设置,包含的 c 文件可能会生成一个单独的目标文件。链接时可能会导致双重定义的符号。

【讨论】:

【参考方案7】:

您可以将 .C 或 .CPP 文件正确包含到其他源文件中。 根据您的 IDE,您通常可以通过查看要包含的源文件属性来防止双重链接,通常通过右键单击它并单击属性,然后取消选中/选中编译/链接/排除构建或任何选项也许。或者您不能在项目本身中包含该文件,因此 IDE 甚至不会知道它存在并且不会尝试编译它。使用makefile,您根本不会将文件放入其中进行编译和链接。

编辑:对不起,我把它作为答案而不是对其他答案的回复:(

【讨论】:

【参考方案8】:

将 C 文件包含到另一个文件中是合法的,但不建议这样做,除非您确切知道为什么要这样做以及要达到什么目的。 我几乎可以肯定,如果您将在此处发布您的问题背后的原因,社区会为您找到另一种更合适的方式来实现您的目标(请注意“几乎”,因为这可能是给定上下文的解决方案)。

顺便说一句,我错过了问题的第二部分。如果 C 文件被包含到另一个文件中并且同时包含到项目中,您可能最终会遇到重复符号问题,为什么要链接对象,即相同的函数将被定义两次(除非它们都是静态的)。

【讨论】:

【参考方案9】:

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

我不知道您对 .prj 文件使用的是什么程序。如果您使用诸如“make”或 Visual Studio 之类的东西,只需确保将其文件列表设置为要编译的文件,而不是无法独立编译的文件。

【讨论】:

【参考方案10】:

许多其他答案不仅仅涵盖了您如何做到这一点,但为什么在正常情况下您可能不应该这样做。也就是说,我会补充一下我过去这样做的原因。

在嵌入式开发中,通常会将芯片供应商源代码作为编译文件的一部分。问题是这些供应商可能没有与您的组织相同的样式指南或标准警告/错误标志设置。

因此,您可以创建一个包含供应商源代码的本地源文件,然后编译此包装 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

【讨论】:

【参考方案11】:

你应该像这样添加标题

#include <another.c>

注意:两个文件应该放在同一个地方

我在用于 ATMEGA 微控制器的 codevison AVR 中使用它并且可以真正工作,但它不能在普通 C 语言文件中工作

【讨论】:

以上是关于在另一个中包含一个 C 源文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在另一个 JavaScript 文件中包含一个 JavaScript 文件?

如何在另一个 javascript 文件中包含 jquery [重复]

在另一个 html 文件中包含 html [重复]

如何在另一个 XAML 文件中包含 XAML 图标

在另一个模板中包含一个 terraform 模板

使用 QML 和 QtQuick2 时如何在另一个 .js 文件中包含 .js 文件?不涉及浏览器