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

查看:82
本文介绍了为什么在编译之前不连接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\n");
    return 0;
}





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

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

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 。有时它被编译为 amalgamation (在构建时从许多源文件中完成)。

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

但是这种方法各有利弊。

But that approach has pros and cons.

显然,编译时间会增加很多。因此,只有在很少编译这些东西的情况下,它才是可行的。

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

这是错误的方法(每个函数只有一个头文件)。对于单人项目(少于十万行代码,aka KLOC = 代码)(至少对于小型项目而言)具有单个通用头文件(您可以预编译(如果使用 GCC ),它将包含所有公共函数的声明和类型,以及静态内联函数的定义(那些足够小且经常调用的函数可以从内联)。例如, 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< gtk / gtk.h> ,其中包括所有内部标头)。相反, 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个)。

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 (通常带有并行通过 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年代设计的,用于比今天您喜欢的笔记本电脑更小,更慢的计算机(通常,当时的内存最多为一兆字节,甚至数百兆字节)千字节,并且计算机的速度至少比今天的手机慢一千倍。

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).

我强烈建议研究源代码并构建一些现有 免费软件项目(例如 GitHub SourceForge 或您最喜欢的Linux发行版)。您会发现它们是不同的方法。请记住,常规习俗和习惯在实践中很重要,所以种不同的方式在 .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

您包括头文件,而不是库(但您应该 链接 库)。但是您可以将它们包含在每个 .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 文件。在另一个极端,原则上您可能没有任何自己的头文件(您仍然需要实现中的头文件,例如< stdio.h> < dlfcn.h> ),然后将重复的代码复制并粘贴到 .c 文件中-例如在每个 .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.

BTW,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 在您的 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 文件)编译成其目标文件(a .o ELF 文件(在Linux上)和链接。使用 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天全站免登陆