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

时间:2012-04-12 14:22:34

标签: c

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

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

我已经四处搜索了,我所能找到的信息都是构建库,这不是我想要做的事情,我不这么认为。我希望我能提供更多帮助,但我不完全确定如何实现这一点 - 我只相信我想要的最终产品。

6 个答案:

答案 0 :(得分:9)

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

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

#include <stdio.h>

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 <stdio.h>

int bar();
void foo();

#endif

N.B。:上面的预处理器代码称为&#34; guard&#34;用于不运行此头文件两次,因此您可以在多个位置调用相同的包含,并且没有编译错误

mylib.c:

#include <mylib.h>

int something() {
    return 42;
}

int bar() {
    return something();
}

void foo(int i) {
    printf("do something with %d\n", i);
}

myprog.c中:

#include <mylib.h>
int main() {
    foo(bar());
    return 0;
}

编译它你做:

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

现在有哪些优势?

  1. 它使您能够逻辑地拆分您的代码,然后找到 代码单位更快
  2. 它可以让你分割你的编辑,和 只修改了修改内容时所需的内容(这是Makefile为你做的)
  3. 它可以让你暴露一些功能并隐藏其他功能(比如 &#34;东西()&#34;在上面的示例中,它有助于为将要阅读您的代码的人(如您的老师)记录您的API;)

答案 1 :(得分:3)

  

是为了便于阅读?

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

首先,请尝试遵守Rob Pike的规则"data dominates":围绕一堆数据结构(通常为struct)设计程序,并对其进行操作。将属于单个数据结构的所有操作放入单独的模块中。使所有函数static不需要被模块外部的函数调用。

答案 2 :(得分:3)

  

是为了便于阅读?

主要原因是

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

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

    < / LI>
  • 可测试性:将功能分解到各自独立的模块中,可以独立于其余代码测试这些功能;您可以更轻松地验证每个功能。

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

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:2)

只是为了给你一个想法。

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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 <stdio.h>
#include <stdlib.h>
#include <string.h>

#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并享受。

答案 5 :(得分:1)

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

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

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

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

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

相关问题