例如,过去的SysInternals工具“ FileMon”具有内核模式驱动程序,其源代码完全位于一个4,000行文件中。与有史以来第一个ping程序相同(〜2,000 LOC)。

#1 楼

使用多个文件总是需要额外的管理开销。必须使用单独的编译和链接阶段来设置构建脚本和/或makefile,确保正确管理不同文件之间的依赖关系,编写“ zip”脚本以通过电子邮件或下载更容易地分发源代码,等等。上。今天的现代IDE通常会承担很多负担,但是我可以肯定的是,在编写第一个ping程序时,尚无此类IDE。对于大约4000 LOC的文件,如果没有这样的IDE可以很好地管理多个文件,则在上述开销和使用多个文件所带来的好处之间进行权衡可能会让人们决定使用单个文件方法。 />

评论


“并且对于大约4000 LOC的文件...”我现在正在作为JS开发人员。当我的文件只有400行代码时,我会担心它的大小! (但是我们的项目中有数十个文件。)

–凯文
17 Mar 3 '17 at 16:06

@Kevin:我头上的一根头发太少了,汤里的一根头发也太多了;-) JS多个文件中的AFAIK不会像“没有现代IDE的C”中那样带来太多的管理开销。

–布朗博士
17 Mar 3 '17 at 16:20

@Kevin JS是完全不同的野兽。每次用户加载网站并且浏览器尚未缓存JS时,JS就会将其传输给最终用户。 C只需将代码传输一次,然后另一端的人对其进行编译并保持编译状态(很明显,这里有例外,但这是一般预期的用例)。 C语言的东西也往往是遗留代码,就像人们在注释中描述的“ 4000行是正常的”项目一样。

–法老王
17年4月4日在1:04

@Kevin现在,看看如何编写underscore.js(1700 loc,一个文件)和许多其他分布的库。实际上,就模块化和部署而言,JavaScript几乎与C一样糟糕。

– Voo
17 Mar 4 '17 at 11:47

@Pharap我认为他的意思是在部署代码之前使用Webpack之类的东西。使用Webpack,您可以处理多个文件,然后将它们编译为一个捆绑包。

–布赖恩·麦卡顿(Brian McCutchon)
17 Mar 5 '17 at 3:20

#2 楼

因为C不擅长模块化。它变得杂乱无章(头文件和#includes,外部函数,链接时错误等),并且引入的模块越多,它就越棘手。因为他们从C的错误中学到了东西,并且使将代码库分解成更小,更简单的单元变得更加容易。但是对于C语言,避免或最小化所有麻烦是有益的,即使这意味着将原本会被认为过多的代码集中到一个文件中。

评论


我认为将C方法描述为“错误”是不公平的。在做出决策时,它们是完全明智且合理的决定。

–杰克·艾德利(Jack Aidley)
17 Mar 3 '17 at 14:43

这些模块化的东西都没有特别复杂。不良的编码风格会使它变得复杂,但并不难理解或实现,而且都不能归类为“错误”。根据Snowman的回答,真正的原因是,过去对多个源文件进行的优化不是那么好,并且FileMon驱动程序需要高性能。另外,与OP的观点相反,这些文件不是特别大。

–格雷厄姆
17 Mar 3 '17 at 14:47

@Graham任何大于1000行代码的文件都应视为代码异味。

–梅森·惠勒
17 Mar 3 '17 at 14:54

@JackAidley一点也不不公平,犯错并不是说当时是一个合理的决定,并不是相互排斥的。鉴于信息不完善和时间有限,错误是不可避免的,应该从不羞耻地隐藏或重新分类以吸取教训中吸取教训。

–杰瑞德·史密斯(Jared Smith)
17 Mar 3 '17 at 15:49

任何声称C的方法不是错误的人都无法理解看似十个线的C文件实际上如何是具有所有头文件#include:d的一万个文件。这意味着项目中的每个文件实际上至少要有一万行,无论“ wc -l”给出的行数是多少。更好地支持模块化,很容易将解析和编译时间缩短到很小的一部分。

– juhist
17年3月3日在18:33

#3 楼

除了历史原因外,在现代的对性能敏感的软件中也有使用它的原因。当所有代码都在一个编译单元中时,编译器可以执行整个程序的优化。使用单独的编译单元,编译器无法以某些方式(例如,内联某些代码)优化整个程序。例如:现代链接器非常擅长隐藏未引用的函数,即使跨多个目标文件也是如此。他们可能还可以执行其他一些优化,但是没有像编译器可以在函数内部进行的优化。
SQLite是一个众所周知的单一源代码模块示例。您可以在“ SQLite合并”页面上阅读有关此内容的更多信息。

1。执行摘要
将100多个单独的源文件连接成一个大的C代码文件,分别名为“ sqlite3.c”和“合并”。
合并包含应用程序嵌入SQLite所需的所有内容。
合并文件的长度超过180,000行,大小超过6
字节。
将SQLite的所有代码组合到一个大文件使SQLite易于部署-只有一个文件可以跟踪。并且
因为所有代码都在一个翻译单元中,编译器可以更好地进行过程间优化,从而使机器代码的速度提高5%至10%。


评论


但是请注意,现代的C编译器可以对多个源文件进行全程序优化(尽管如果先将它们编译为单个目标文件则不能)。

–戴维斯洛
17 Mar 3 '17 at 3:35



@Davislor看一下典型的构建脚本:编译器实际上不会这样做。

–user22815
17 Mar 3 '17 at 4:11

将构建脚本更改为$(CC)$(CFLAGS)$(LDFLAGS)-o $(TARGET)$(CFILES)明显比将所有内容移动到单个soudce文件要容易得多。您甚至可以将整个程序编译为传统构建脚本的替代目标,该传统构建脚本跳过重新编译未更改的源文件,类似于人们可能会关闭生产目标的性能分析和调试。如果一切都在一个大堆中,那么您就没有这种选择。这不是人们习惯的,但没有任何麻烦。

–戴维斯洛
17 Mar 3 '17 at 6:47



当您将代码“编译”为单个目标文件时(取决于“编译”对您的含义),@ Davislor整个程序优化/链接时优化(LTO)也起作用。例如,GCC的LTO将在编译时将其已解析的代码表示形式添加到各个目标文件中,并且在链接时将使用该代码表示​​形式而不是(也存在)目标代码来重新编译并构建整个程序。因此,这适用于首先编译为单个目标文件的构建设置,尽管初始编译生成的机器代码将被忽略。

–梦想家
17 Mar 3 '17 at 7:54

如今,JsonCpp也这样做。关键是在开发过程中文件不是这种方式。

–轨道轻赛
17年3月3日在11:01

#4 楼

除了其他受访者提到的简单性因素外,许多C程序是由一个人编写的。

当您有一个团队时,最好将应用程序拆分为几个源文件,以避免代码更改中的不必要冲突。尤其是当有高级和非常初级的程序员在从事该项目时。

当一个人独自工作时,这不是问题。

我个人使用基于功能的多个文件。但这就是我。

评论


@OskarSkog但是,您永远不会在将来与自己修改文件的同时进行修改。

–Loren Pechtel
17 Mar 5 '17 at 5:13

#5 楼

因为C89没有inline功能。这意味着将文件分解为函数会导致将值压入堆栈并四处移动的开销。这在1个大switch语句(事件循环)中实现代码时增加了相当多的开销。但是,与更模块化的解决方案相比,事件循环始终要高效(甚至正确)实现起来要困难得多。因此,对于大型项目,人们仍然会选择模块化。但是,当他们预先考虑了设计并可以在1条switch语句中控制状态时,他们选择了这一点。

如今,即使在C语言中,也不必牺牲性能来模块化,因为即使在C函数中也可以内联。

评论


在89天内,C函数的内联量可能会达到如今的水平,内联几乎不应该被使用-在几乎所有情况下,编译器都比您了解得更多。而且,这些4k LOC文件大多数都不是一个巨大的功能-这是一种可怕的编码风格,也不会带来任何明显的性能优势。

– Voo
17 Mar 4 '17 at 11:55

@Voo,我不知道您为什么提到编码风格。我不是在提倡它。实际上,我提到在大多数情况下,由于实施拙劣,它保证了解决方案效率较低。我还提到这是一个坏主意,因为它无法扩展(适用于较大的项目)。话虽如此,在非常紧密的循环中(这是在接近于硬件的网络代码中发生的情况),不必要地将值压入/弹出堆栈(在调用函数时)会增加运行程序的成本。这不是一个很好的解决方案。但这是当时最好的。

–德米特里·鲁巴诺维奇(Dmitry Rubanovich)
17 Mar 4 '17 at 12:29



强制性注释:inline关键字与内联优化几乎没有关系。这并不是编译器进行优化的特殊提示,而是与重复符号的链接有关。

–氢化物
17 Mar 5 '17 at 16:01

@Dmitry关键是声称由于C89编译器中没有inline关键字而无法内联,这就是为什么您必须在一个巨型函数中编写所有内容的原因是不正确的。您几乎应该永远不要将内联用作性能优化-不管怎样编译器通常都会比您更了解(并且也可以忽略关键字)。

– Voo
17 Mar 5 '17 at 16:58

@Voo:一个程序员和一个编译器通常会彼此知道一些其他东西。 inline关键字具有与链接器相关的语义,比与是否执行内联优化这一问题更重要,但是某些实现具有其他指令来控制内联,而这些事情有时可能非常重要。在某些情况下,函数看起来太大了,不值得内联,但是不断折叠可以将大小和执行时间减少到几乎没有。没有大力鼓励内联的编译器可能不会...

–超级猫
17 Mar 5 '17 at 22:33

#6 楼

这只是进化的一个例子,令我惊讶的是,至今还没有提及。

在编程的黑暗日子里,单个FILE的编译可能要花几分钟。如果程序是模块化的,那么包含必要的头文件(没有预编译的头文件选项)将是导致速度下降的另一个重要原因。此外,编译器可能选择/需要在磁盘本身上保留一些信息,这可能没有自动交换文件的好处。随着时间的推移慢慢适应。

那时,使用单个文件所获得的收益类似于我们使用SSD而不是HDD获得的收益。