gpt4 book ai didi

c - 为什么不在编译前连接 C 源文件?

转载 作者:行者123 更新时间:2023-12-01 17:19:11 27 4
gpt4 key购买 nike

这个问题在这里已经有了答案:





#include all .cpp files into a single compilation unit?

(6 个回答)



The benefits / disadvantages of unity builds? [duplicate]

(3 个回答)


4年前关闭。




我来自脚本背景,C 中的预处理器在我看来总是很丑陋。尽管如此,当我学习编写小型 C 程序时,我还是接受了它。我只是真正使用预处理器来包含我为自己的函数编写的标准库和头文件。

我的问题是,为什么 C 程序员不直接跳过所有包含,而是简单地连接他们的 C 源文件然后编译它?如果您将所有包含放在一个地方,您只需定义一次您需要的内容,而不是在所有源文件中。

这是我所描述的一个例子。这里我有三个文件:

// includes.c
#include <stdio.h>
// main.c
int main() {
foo();
printf("world\n");
return 0;
}
// foo.c
void foo() {
printf("Hello ");
}

通过做类似 cat *.c > to_compile.c && gcc -o myprogram to_compile.c 的事情在我的 Makefile 中,我可以减少我编写的代码量。

这意味着我不必为我创建的每个函数编写头文件(因为它们已经在主源文件中),这也意味着我不必在我创建的每个文件中包含标准库。这对我来说似乎是个好主意!

然而,我意识到 C 是一种非常成熟的编程语言,我想象着比我聪明得多的其他人已经有了这个想法并决定不使用它。为什么不?

最佳答案

有些软件就是这样构建的。

一个典型的例子是SQLite。它有时被编译为amalgamation(在构建时从许多源文件中完成)。

但这种方法有利也有弊。

显然,编译时间会增加不少。所以只有当你很少编译这些东西时它才是实用的。

也许,编译器可能会优化更多。但是通过链接时间优化(例如,如果使用最新的 GCC,编译和链接gcc -flto -O2)您可以获得相同的效果(当然,以增加构建时间为代价)。

I don't have to write a header file for each function



这是一种错误的方法(每个函数有一个头文件)。对于一个单人项目(少于 10 万行代码,又名 KLOC = code的千行),拥有一个公共(public)头文件(你可以 pre-compile,如果使用 GCC),它将包含所有公共(public)函数和类型的声明,以及 static inline函数的定义(那些足够小且调用频率足够高以从 inlining中获利的函数)。例如, sash shell就是这样组织的( lout formatter也是如此,52 KLOC)。

您可能还有一些头文件,并且可能有一些单独的“分组”头文件,其中 #include -s 全部(并且您可以预编译)。例如,参见 jansson(它实际上有一个公共(public)头文件)和 GTK(它有很多内部头文件,但大多数使用它的应用程序 have只有一个 #include <gtk/gtk.h>,它又包含所有内部头文件) .另一方面, POSIX有很多头文件,它记录了应该包含哪些以及以什么顺序包含的头文件。

有些人喜欢有很多头文件(有些人甚至喜欢将单个函数声明放在自己的头文件中)。我不知道(对于个人项目,或者只有两三个人会提交代码的小项目),但是 的品味问题。顺便说一句,当一个项目增长很多时,头文件集(和翻译单元)经常发生显着变化。还要查看 REDIS(它有 139 个 .h头文件和 214 个 .c文件,即总共 126 个 KLOC 的翻译单元)。

拥有一个或多个 translation units也是一个品味问题(以及便利性、习惯和惯例)。我的偏好是拥有不太小的源文件(即翻译单元),通常每个文件有几千行,并且通常(对于小于 60 KLOC 的小项目)有一个通用的单个头文件。不要忘记使用诸如 build automation之类的 GNU make工具(通常使用 parallel构建到 make -j;然后您将有多个编译进程同时运行)。拥有这种源文件组织的优点是编译速度相当快。顺便说一句,在某些情况下, metaprogramming方法是值得的:您的一些(内部标题或翻译单元)C“源”文件可能由其他东西生成(例如 AWK中的一些脚本,一些专门的 C 程序,如 bison或您自己的东西)。

请记住,C 是在 1970 年代设计的,用于比您今天最喜欢的笔记本电脑更小、更慢的计算机(通常,当时的内存最多为 1 兆字节,甚至几百 KB,而计算机至少要慢一千倍比你今天的手机)。

我强烈建议 研究源代码并构建一些现有的free software项目(例如 GitHubSourceForge或您最喜欢的Linux发行版上的项目)。您将了解到它们是不同的方法。请记住,C 约定和习惯中的 在实践中很重要,因此 有不同的方法可以在.c.h文件中组织您的项目。阅读 C preprocessor

It also means I don't have to include the standard libraries in each file I create



您包含头文件,而不是库(但您应该包含 link库)。但是您可以将它们包含在每个 .c文件中(许多项目都在这样做),或者您可以将它们包含在一个单独的头文件中并预编译该头文件,或者您可以有十几个头文件并将它们包含在系统头文件之后在每个编译单元中。 YMMV。 请注意,当今计算机上的预处理时间很快(至少,当您要求编译器优化时,因为优化比解析和预处理花费的时间更多)。

请注意,某些 #include-d 文件中的内容是常规的(并且未由 C 规范定义)。一些程序在一些这样的文件中有他们的一些代码(它们不应该被称为“头文件”,只是一些“包含的文件”;然后不应该有 .h后缀,而是像 .inc这样的其他东西) .例如查看 XPM文件。在另一个极端,您原则上可能没有任何自己的头文件(您仍然需要来自实现的头文件,例如来自您的 POSIX 系统的 <stdio.h><dlfcn.h>)并将重复的代码复制并粘贴到您的 .c中” 文件 -eg在每个 int foo(void);文件中都有一行 .c,但这是非常糟糕的做法,并且不受欢迎。但是,一些程序正在生成共享一些公共(public)内容的 C 文件。

顺便说一句,C 或 C++14 没有模块(就像 OCaml 一样)。换句话说,在 C 中,模块主要是一种约定。

(请注意,拥有成千上万个只有几十行的非常小的 .h.c文件可能会大大减慢您的构建时间;就构建而言,拥有数百个每个几百行的文件更为合理时间。)

如果你开始用 C 语言处理一个单人项目,我建议首先有一个头文件(并预编译它)和几个 .c翻译单元。在实践中,您将比 .c文件更频繁地更改 .h文件。一旦您拥有 10 个以上的 KLOC,您可能会将其重构为多个头文件。这种重构设计起来很棘手,但很容易做到(只需复制和粘贴大量代码)。其他人会有不同的建议和提示(没关系!)。但是不要忘记在编译时启用所有警告和调试信息(所以用 gcc -Wall -g编译,也许在你的 CFLAGS= -Wall -g中设置 Makefile)。使用 gdb调试器(和 valgrind...)。当您对已调试的程序进行基准测试时,要求进行优化( -O2)。还可以使用像 Git这样的版本控制系统。

相反,如果你正在设计一个由几个人一起工作的更大的项目,最好有几个文件——甚至是几个头文件——(直观地说,每个文件都有一个人主要负责,其他人做次要的)对该文件的贡献)。

在评论中,您添加:

I'm talking about writing my code in lots of different files but using a Makefile to concatenate them



我不明白为什么这会有用(除非在非常奇怪的情况下)。将每个翻译单元(例如每个 .c文件)编译成它的 object file(Linux 上的 .o ELF文件)和 link更好(也是非常常见的做法)。这对于 make很容易(实际上,当你只更改一个 .c文件时,例如修复一个错误,只有那个文件被编译并且增量构建非常快),你可以要求它编译对象文件在 parallel中使用 make -j(然后你的构建在你的多核处理器上运行得非常快)。

关于c - 为什么不在编译前连接 C 源文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42135503/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com