为什么不在编译前连接 C 源文件? [英] Why not concatenate C source files before compilation?

查看:23
本文介绍了为什么不在编译前连接 C 源文件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I come from a scripting background and the preprocessor in C has always seemed ugly to me. None the less I have embraced it as I learn to write small C programs. I am only really using the preprocessor for including the standard libraries and header files I have written for my own functions.

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

My question is why don't C programmers just skip all the includes and simply concatenate their C source files and then compile it? If you put all of your includes in one place you would only have to define what you need once, rather than in all your source files.

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

Here's an example of what I'm describing. Here I have three files:

// includes.c
#include <stdio.h>

// main.c
int main() {
    foo();
    printf("world
");
    return 0;
}

// foo.c
void foo() {
    printf("Hello ");
}

通过像 cat *.c >to_compile.c &&gcc -o myprogram to_compile.c 在我的 Makefile 中我可以减少我编写的代码量.

By doing something like cat *.c > to_compile.c && gcc -o myprogram to_compile.c in my Makefile I can reduce the amount of code I write.

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

This means that I don't have to write a header file for each function I create (because they're already in the main source file) and it also means I don't have to include the standard libraries in each file I create. This seems like a great idea to me!

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

However I realise that C is a very mature programming language and I'm imagining that someone else a lot smarter than me has already had this idea and decided not to use it. Why not?

推荐答案

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

Some software are built that way.

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

A typical example is SQLite. It is sometimes compiled as an amalgamation (done at build time from many source files).

但这种方法有利有弊.

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

Obviously, the compile time will increase by quite a lot. So it is practical only if you compile that stuff rarely.

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

Perhaps, the compiler might optimize a bit more. But with link time optimizations (e.g. if using a recent GCC, compile and link with gcc -flto -O2) you can get the same effect (of course, at the expense of increased build time).

我不必为每个函数都写一个头文件

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

这是一种错误的方法(每个函数有一个头文件).对于单人项目(少于十万行代码,又名 KLOC = 千行 代码),这是很合理的——至少对于小项目来说——有一个单个通用头文件(你可以预编译(如果使用GCC),它将包含所有公共函数的声明和类型,也许还有 static inline 函数的定义(那些足够小并且被频繁调用以从 内联).例如,sash shell 就是这样组织的(lout 格式化程序,与 52 KLOC).

That is a wrong approach (of having one header file per function). For a single-person project (of less than a hundred thousand lines of code, a.k.a. KLOC = kilo line of code), it is quite reasonable -at least for small projects- to have a single common header file (which you could pre-compile if using GCC), which will contain declarations of all public functions and types, and perhaps definitions of static inline functions (those small enough and called frequently enough to profit from inlining). For example, the sash shell is organized that way (and so is the lout formatter, with 52 KLOC).

您可能还有一些头文件,并且可能有一些单独的分组"头文件,#include -s 所有头文件(并且您可以预编译).参见例如 jansson(实际上有一个 public 头文件)和GTK(具有很多的内部标头,但大多数应用程序使用它只有一个 #include 其中转包括所有内部标题).另一方面,POSIX 有很多头文件,它记录了哪些应该是包括在内以及按什么顺序.

You might also have a few header files, and perhaps have some single "grouping" header which #include-s all of them (and which you could pre-compile). See for example jansson (which actually has a single public header file) and GTK (which has lots of internal headers, but most applications using it have just one #include <gtk/gtk.h> which in turn include all the internal headers). On the opposite side, POSIX has a big lot of header files, and it documents which ones should be included and in which order.

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

Some people prefer to have a lot of header files (and some even favor putting a single function declaration in its own header). I don't (for personal projects, or small projects on which only two or three persons would commit code), but it is a matter of taste. BTW, when a project grows a lot, it happens quite often that the set of header files (and of translation units) changes significantly. Look also into REDIS (it has 139 .h header files and 214 .c files i.e. translation units totalizing 126 KLOC).

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

Having one or several translation units is also a matter of taste (and of convenience and habits and conventions). My preference is to have source files (that is translation units) which are not too small, typically several thousand lines each, and often have (for a small project of less than 60 KLOC) a common single header file. Don't forget to use some build automation tool like GNU make (often with a parallel build through make -j; then you'll have several compilation processes running concurrently). The advantage of having such a source file organization is that compilation is reasonably quick. BTW, in some cases a metaprogramming approach is worthwhile: some of your (internal header, or translation units) C "source" files could be generated by something else (e.g. some script in AWK, some specialized C program like bison or your own thing).

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

Remember that C was designed in the 1970s, for computers much smaller and slower than your favorite laptop today (typically, memory was at that time a megabyte at most, or even a few hundred kilobytes, and the computer was at least a thousand times slower than your mobile phone today).

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

I strongly suggest to study the source code and build some existing free software projects (e.g. those on GitHub or SourceForge or your favorite Linux distribution). You'll learn that they are different approaches. Remember that in C conventions and habits matter a lot in practice, so there are different ways to organize your project in .c and .h files. Read about the C preprocessor.

这也意味着我不必在我创建的每个文件中都包含标准库

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

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

You include header files, not libraries (but you should link libraries). But you could include them in each .c files (and many projects are doing that), or you could include them in one single header and pre-compile that header, or you could have a dozen of headers and include them after system headers in each compilation unit. YMMV. Notice that preprocessing time is quick on today's computers (at least, when you ask the compiler to optimize, since optimizations takes more time than parsing & preprocessing).

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

Notice that what goes into some #include-d file is conventional (and is not defined by the C specification). Some programs have some of their code in some such file (which should then not be called a "header", just some "included file"; and which then should not have a .h suffix, but something else like .inc). Look for example into XPM files. At the other extreme, you might in principle not have any of your own header files (you still need header files from the implementation, like <stdio.h> or <dlfcn.h> from your POSIX system) and copy and paste duplicated code in your .c files -e.g. have the line int foo(void); in every .c file, but that is very bad practice and is frowned upon. However, some programs are generating C files sharing some common content.

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

BTW, C or C++14 do not have modules (like OCaml has). In other words, in C a module is mostly a convention.

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

(notice that having many thousands of very small .h and .c files of only a few dozen lines each may slow down your build time dramatically; having hundreds of files of a few hundred lines each is more reasonable, in term of build time.)

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

If you begin to work on a single-person project in C, I would suggest to first have one header file (and pre-compile it) and several .c translation units. In practice, you'll change .c files much more often than .h ones. Once you have more than 10 KLOC you might refactor that into several header files. Such a refactoring is tricky to design, but easy to do (just a lot of copy&pasting chunk of codes). Other people would have different suggestions and hints (and that is ok!). But don't forget to enable all warnings and debug information when compiling (so compile with gcc -Wall -g, perhaps setting CFLAGS= -Wall -g in your Makefile). Use the gdb debugger (and valgrind...). Ask for optimizations (-O2) when you benchmark an already-debugged program. Also use a version control system like Git.

相反,如果您正在设计一个几个人可以工作的更大的项目,最好有几个文件——甚至是几个头文件——(直觉上,每个文件都有一个主要负责的人,其他人对该文件的贡献很小).

On the contrary, if you are designing a larger project on which several persons would work, it could be better to have several files -even several header files- (intuitively, each file has a single person mainly responsible for it, with others making minor contributions to that file).

在评论中,您添加:

我说的是在许多不同的文件中编写我的代码,但使用 Makefile 将它们连接起来

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

我不明白为什么这会有用(除非在非常奇怪的情况下).将每个翻译单元(例如每个 .c 文件)编译成它的 目标文件(.o Linux 上的 ELF 文件)并稍后链接.make 很容易做到这一点(实际上,当你只更改一个 .c 文件时,例如修复一个错误,只有那个文件被编译,增量构建才是真正的快速),您可以要求它在 并行 使用 make -j(然后你的构建在你的多核处理器上运行得非常快).

I don't see why that would be useful (except in very weird cases). It is much better (and very usual and common practice) to compile each translation unit (e.g. each .c file) into its object file (a .o ELF file on Linux) and link them later. This is easy with make (in practice, when you'll change only one .c file e.g. to fix a bug, only that file gets compiled and the incremental build is really quick), and you can ask it to compile object files in parallel using make -j (and then your build goes really fast on your multi-core processor).

这篇关于为什么不在编译前连接 C 源文件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆