组织C文件

时间:2008-09-06 22:58:35

标签: c header file-organization

我习惯在一个C文件中完成所有编码。但是,我正在开展一个足够大的项目,这样做变得不切实际。我一直在#including他们,但我遇到了我多次#include一些文件的情况,等等。我听说过.h文件,但我不确定他们的功能是什么(或者为什么有2个文件比1)好。

我应该使用哪些策略来组织代码?是否可以将“公共”功能与特定文件的“私有”功能分开?

This问题促成了我的询问。 tea.h文件没有引用tea.c文件。编译器是否“知道”每个.h文件都有一个相应的.c文件?

8 个答案:

答案 0 :(得分:35)

您应该将.h文件视为.c文件的接口文件。每个.c文件代表一个具有一定功能的模块。如果.c文件中的函数被其他模块(即其他.c文件)使用,则将函数原型放在.h接口文件中。通过将接口文件包含在原始模块.c文件和其他所有.c文件中,您需要使用该功能,您可以将此功能用于其他模块。

如果您只需要某个.c文件中的函数(不在任何其他模块中),请将其范围声明为static。这意味着它只能从定义的c文件中调用。

对于跨多个模块使用的变量也是如此。它们应该放在头文件中,并且必须用关键字'extern'标记。注意:对于函数,关键字“extern”是可选的。功能始终被视为“外部”。

头文件中的包含保护有助于多次不包含相同的头文件。

例如:

Module1.c:

    #include "Module1.h"

    static void MyLocalFunction(void);
    static unsigned int MyLocalVariable;    
    unsigned int MyExternVariable;

    void MyExternFunction(void)
    {
        MyLocalVariable = 1u;       

        /* Do something */

        MyLocalFunction();
    }

    static void MyLocalFunction(void)
    {
      /* Do something */

      MyExternVariable = 2u;
    }

Module1.h:

    #ifndef __MODULE1.H
    #define __MODULE1.H

    extern unsigned int MyExternVariable;

    void MyExternFunction(void);      

    #endif

Module2.c

    #include "Module.1.h"

    static void MyLocalFunction(void);

    static void MyLocalFunction(void)
    {
      MyExternVariable = 1u;
      MyExternFunction();
    }

答案 1 :(得分:10)

尝试使每个.c专注于特定的功能区域。使用相应的.h文件来声明这些函数。

每个.h文件都应该有一个“标题”保护它的内容。例如:

#ifndef ACCOUNTS_H
#define ACCOUNTS_H
....
#endif

通过这种方式,您可以根据需要多次添加“accounts.h”,并且第一次在特定编译单元中看到它将是唯一实际提取其内容的内容。

答案 2 :(得分:7)

要启动几条简单的规则:

  1. 将您想要“public”的声明放入您正在创建的C实现文件的头文件中。
  2. 只有#include C文件中实现C文件所需的头文件。
  3. 只有在该头文件中的声明需要时,
  4. 才在头文件中包含头文件。

  5. 使用Andrew描述的include guard方法或使用 #pragma once 如果编译器支持它(它做同样的事情 - 有时更有效率)

答案 3 :(得分:7)

编译器

你可以在this topic看到一个C'模块的例子 - 注意有两个文件 - 标题tea.h和代码tea.c.您声明了其他程序要在标头中访问的所有公共定义,变量和函数原型。在您的主项目中,您将#include,并且该代码现在可以访问标题中提到的茶模块的函数和变量。

之后会变得有点复杂。如果您正在使用Visual Studio和许多其他IDE来管理您的构建,那么请忽略此部分 - 它们负责编译和链接对象。

链接器

编译两个单独的C文件时,编译器会生成单独的目标文件 - 因此main.c变为main.o,而tea.c变为tea.o.链接器的工作是查看所有目标文件(你的main.o和tea.o),并匹配引用 - 所以当你在main中调用tea函数时,链接器会修改该调用,所以它实际上调用了右边的在茶中的功能。链接器生成可执行文件。

有一个great tutorial在这个主题上有更深入的内容,包括你将遇到的范围和其他问题。

祝你好运!

- 亚当

答案 4 :(得分:3)

回答您的其他问题:

  

This   问题促成了我的询问。该   tea.h文件没有引用   tea.c文件。编译器是否“知道”   每个.h文件都有一个对应的   .c文件?

编译器主要不关心头文件。每次调用编译器都会将源(.c)文件编译为对象(.o)文件。在幕后(即在make文件或项目文件中)生成与此等效的命令行:

compiler --options tea.c

源文件#include是它引用的资源的所有头文件,这是编译器查找头文件的方式。

(我在这里略过一些细节。有关构建C项目的知识有很多。)

答案 5 :(得分:3)

除了上面提供的答案之外,将代码分成模块(单独的文件)的一个小优点是,如果必须有任何全局变量,可以通过使用它来将其范围限制为单个模块。关键词'静态'。 (您也可以将其应用于函数)。请注意,'static'的使用与在函数内部的使用不同。

答案 6 :(得分:1)

你的问题清楚表明你并没有真正做过多认真的发展。通常的情况是,您的代码通常太大而无法放入一个文件中。一个好的规则是你应该将功能分成逻辑单元(.c文件),每个文件应该只包含你一次可以轻松掌握的功能。

然后,给定的软件产品通常包括来自许多不同.c文件的输出。通常这样做是因为编译器产生了许多目标文件(在unix系统中“.o”文件,VC生成.obj文件)。 “链接器”的目的是将这些目标文件组合成输出(共享库或可执行文件)。

通常,您的实现(.c)文件包含实际的可执行代码,而头文件(.h)在这些实现文件中具有公共函数的声明。您可以很容易地获得比实现文件更多的头文件,有时头文件也可以包含内联代码。

实现文件相互包含通常很不寻常。一个好的做法是确保每个实现文件将其关注点与其他文件分开。

我建议你下载并查看linux内核的源代码。对于C程序而言,这是相当庞大的,但是组织成单独的功能区域。

答案 7 :(得分:0)

.h文件应该用于定义函数的原型。这是必要的,因此您可以在C文件中包含所需的原型,而无需在一个文件中声明所有需要的功能。

例如,当您#include <stdio.h>时,它提供了printf和其他IO功能的原型。这些函数的符号通常由编译器默认加载。如果您对涉及这些文件的正常习语感兴趣,可以查看/ usr / include下的系统.h文件。

如果您只编写功能不多的简单应用程序,则不必将所有内容模块化为逻辑程序分组。但是,如果您需要开发大型系统,那么您需要考虑在何处定义每个功能。

相关问题