如何将单片程序拆分为较小的单独文件?

在我看到的所有代码中,程序总是被分解成许多较小的文件。 对于我在学校的所有项目,我已经得到了一个巨大的C源文件,其中包含我使用的所有结构和函数。

我想学习怎么做是将我的程序分成更小的文件,这似乎是专业的标准。 (顺便说一句,为什么这只是为了方便阅读?)

我一直在搜索,我所能找到的信息都是建立图书馆,这不是我想要做的事情。 我希望我能提供更多帮助,但我不完全确定如何实现这一点 – 我只确定我想要的最终产品。

嗯,这正是你想要的:将你的代码分成几个库!

让我们举一个例子,你有一个文件:

#include  int something() { return 42; } int bar() { return something(); } void foo(int i) { printf("do something with %d\n", i); } int main() { foo(bar()); return 0; } 

你可以把它分成:

mylib.h:

 #ifndef __MYLIB_H__ #define __MYLIB_H__ #include  int bar(); void foo(); #endif 

注意:上面的预处理器代码称为“guard”,用于不运行此头文件两次,因此您可以在多个位置调用相同的include,并且没有编译错误

mylib.c:

 #include  int something() { return 42; } int bar() { return something(); } void foo(int i) { printf("do something with %d\n", i); } 

myprog.c中:

 #include  int main() { foo(bar()); return 0; } 

编译它你做:

 gcc -c mylib.c -I./ gcc -o myprog myprog.c -I./ mylib.o 

现在的优势?

  1. 它使您能够逻辑地拆分代码,然后更快地找到代码单元
  2. 它使您能够拆分编译,并仅重新编译修改内容时所需的内容(这是Makefile为您所做的)
  3. 它使您能够公开一些function并隐藏其他function(例如上面示例中的“something()”),它有助于为将要读取您的代码的人(如您的老师)记录您的API;)

它只是为了方便阅读?

主要原因是

  • 可维护性:在您描述的大型单片程序中,存在更改文件某一部分中的代码可能会在其他位置产生意外影响的风险。 回到我的第一份工作,我们的任务是加速驱动3D图形显示的代码。 它是一个单一的,单一的,5000 +行的mainfunction(在宏大的计划中并不是那么大,但足够令人头疼), 我们所做的每一次改变都打破了其他地方的执行路径。 这是编写错误的代码( goto s galore,字面上数百个单独的变量,具有令人难以置信的信息名称,如nv001x ,程序结构读起来像老式BASIC,微优化没有做任何事情,但使代码阅读起来要困难得多,但是把它全部放在一个文件中会让情况变得更糟。 我们最终放弃并告诉客户我们要么必须从头开始重写整个事情,要么他们必须购买更快的硬件。 他们最终买了更快的硬件。

  • 可重用性:一遍又一遍地编写相同的代码是没有意义的。 如果您想出一个通常有用的代码(例如,XML解析库或通用容器),请将其保存在自己单独编译的源文件中,并在必要时将其链接。

  • 可测试性:将function分解为各自独立的模块允许您独立于其余代码测试这些function; 您可以更轻松地validation每个function。

  • 可构建性:好的,所以“可构建性”不是一个真正的单词,但每次更改一行或两行时从头开始重建整个系统可能非常耗时。 我已经在非常大的系统上工作,完整的构建可能需要几个小时。 通过分解代码,您可以限制必须重建的代码量。 更不用说任何编译器都会对它可以处理的文件大小有一些限制。 我上面提到的图形驱动程序? 我们试图加快速度的第一件事就是在打开优化的情况下编译它(从O1开始)。 编译器会占用所有可用内存,然后它会占用所有可用的交换,直到内核发生恐慌并导致整个系统崩溃。 我们实际上无法在打开任何优化的情况下构建该代码(这是在128 MB是非常昂贵的内存的时代)。 如果代码被分解成多个文件(地狱,只是同一个文件中的多个函数 ),我们就不会遇到这个问题。

  • 并行开发:没有“能力”这个词,但通过将源分解为多个文件和模块,您可以并行化开发。 我在一个文件上工作,你在另一个文件上工作,其他人在第三个上工作,等等。我们不会冒险踩到彼此的代码。

它只是为了方便阅读?

不,它还可以节省你很多时间编译; 当您更改一个源文件时,您只需重新编译该文件,然后重新链接,而不是重新编译所有内容。 但重点是将程序划分为一组分离良好的模块,这些模块比单个单片“blob”更容易理解和维护。

对于初学者来说,尽量遵守Rob Pike的“数据占主导地位”的规则:围绕一堆数据结构(通常是struct )设计程序,并对其进行操作。 将属于单个数据结构的所有操作放入单独的模块中。 使所有函数static ,不需要由模块外部的函数调用。

易于阅读是分解文件的一个方面,但另一个方面是,当您构建包含多个文件(头文件和源文件)的项目时,良好的构建系统将只重建已修改的文件,从而缩短构建时间。

至于如何将单个文件分解为多个文件,还有很多方法可以解决。 对我来说,我会尝试对function进行分组,例如,所有输入处理都放在一个源文件中,在另一个源文件中输出,以及在第三个源文件中由许多不同函数使用的函数。 我会对结构/常量/宏,组相关结构/等做同样的事情。 在单独的头文件中。 我还将仅在单个源文件中使用的函数标记为static ,因此不能错误地使用其他源文件。

只是为了给你一个主意。

创建一个名为print.c的文件,将其放入:

 #include  #include  #include  void print_on_stdout(const char *msg) { if (msg) fprintf(stdout, "%s\n", msg); } void print_on_stderr(const char *msg) { if (msg) fprintf(stderr, "%s\n", msg); } 

创建一个名为print.h的文件,将其放入:

 void print_on_stdout(const char *msg); void print_on_stderr(const char *msg); 

创建一个名为main.c的文件,将其放入:

 #include  #include  #include  #include "print.h" int main() { print_on_stdout("test on stdout"); print_on_stderr("test on stderr"); return 0; } 

现在,对于每个C文件,编译为:

 gcc -Wall -O2 -o print.o -c print.c gcc -Wall -O2 -o main.o -c main.c 

然后链接编译的文件以生成可执行文件:

 gcc -Wall -O2 -o test print.o main.o 

运行./test并享受。

好吧,我不是专家,但我总是试着在更大的实体中思考一个函数。 如果我有一组逻辑上属于一起的函数,我将它放在一个单独的文件中。 通常,如果function类似,并且有人想要这样的function之一,他可能也需要该组中的一些其他function。

分割单个文件的需要来自于为文件使用不同文件夹的相同原因:人们希望在众多function上拥有一些逻辑组织,这样他们就不需要为大型单个源文件进行grep找到所需的一个。 这样,当您考虑/开发一些固定部分时,您可以忘记程序中不相关的部分。

拆分的另一个原因可能是你可以通过在标题中没有提到它来隐藏代码的其余部分的一些内部函数。 这种方式明确地将内部函数(仅在.c文件中需要)与有趣的函数分离到程序的外部“Universe”。

一些更高级的语言甚至将“function归属于一起”的概念扩展为“作用于同一事物的function,作为一个实体呈现” – 并称之为一个

拆分的另一个历史原因是单独的编译function。 如果您的编译器很慢(例如,通常是C ++的情况),将代码拆分为多个文件意味着如果只修改一个位置,则只需要重新编译一个文件以获取更改的可能性很高。 由于现代C编译器与典型处理器速度相比并不慢,因此对您来说这可能不是问题。