3

所以我仍然习惯于模块化编程,并希望确保我遵守最佳实践。如果我有下面的两个模块头文件,#included每个文件的头文件(例如“mpi.h”)会被包含多次吗?有没有适当的方法来解决这个问题?

此外,我的模块标题通常看起来像这些示例,因此任何其他批评/指针都会有所帮助。

/* foo.h */
#ifndef FOO_H
#define FOO_H

#include <stdlib.h>
#include "mpi.h"

void foo();

#endif

/* bar.h */
#ifndef BAR_H
#define BAR_H

#include <stdlib.h>
#include "mpi.h"

void bar();

#endif

并使用示例程序:

/* ExampleClient.c */
#include <stdlib.h>
#include <stdio.h>
#include "mpi.h"
#include "foo.h"
#include "bar.h"

void main(int argc, char *argv[]) {
    foo();
    MPI_Func();
    bar();
    exit(0)
}
4

4 回答 4

5

“包括”是什么意思?预处理器语句#include file复制内容file并用这些内容替换语句。不管发生这种情况

如果“包含”是指“这些文件中的语句和符号将被多次解析,从而导致警告和错误”,那么不,包含防护将阻止这种情况。

如果“包含”是指“编译器的某些部分将读取这些文件的某些部分”,那么是的,它们将被包含多次。由于包含保护,预处理器将读取文件的第二个包含并用空行替换它,这会产生很小的开销(文件已经在内存中)。现代编译器(GCC,不确定其他编译器)可能会被优化以避免这种情况,但是请注意,该文件在第一遍包含保护,并简单地丢弃未来的包含,消除开销 - 不要担心这里的速度,清晰度和模块化更为重要。当然,编译是一个耗时的过程,但#include您最不必担心。

为了更好地理解包含守卫,请考虑以下代码示例:

#ifndef INCLUDE_GUARD
#define INCLUDE_GUARD
// Define to 1 in first block
#define GUARDED 1
#endif

#ifndef INCLUDE_GUARD
#define INCLUDE_GUARD
// Redefine to 2 in second block
#define GUARDED 2
#endif

在(第一次)预处理之后,将GUARDED定义什么?如果确实定义了它们的参数,则预处理器语句#ifndef或其等效语句#if !defined()将返回。false因此,我们可以得出结论,第二个#ifndef将返回 false,因此在预处理器的第一次传递之后,只有 GUARDED 的第一个定义将保留。程序中剩余的任何实例GUARDED将在下一次通过时被 1 替换。

在您的示例中,您有一些稍微(但不是很多)更复杂的东西。展开 ExampleClient.c 中的所有#include语句将得到以下源:(注意:我缩进了它,但这不是标题的正常样式,预处理器不会这样做。我只是想让它更具可读性)

/* ExampleClient.c */
//#include <stdlib.h>
  #ifndef STDLIB_H
    #define STDLIB_H
    int abs (int number); //etc.
  #endif

//#include <stdio.h>
  #ifndef STDLIB_H
    #define STDLIB_H
    #define NULL 0 //etc.
  #endif

//#include "mpi.h"
  #ifndef MPI_H
    #define MPI_H
    void MPI_Func(void);
  #endif

//#include "foo.h"
  #ifndef FOO_H
    #define FOO_H
    //#include <stdlib.h>
      #ifndef STDLIB_H
        #define STDLIB_H
        int abs (int number); //etc.
      #endif
    //#include "mpi.h"
      #ifndef MPI_H
        #define MPI_H
        void MPI_Func(void);
      #endif
    void foo(void);
  #endif


//#include "bar.h"
  #ifndef BAR_H
    #define BAR_H
    //#include <stdlib.h>
      #ifndef STDLIB_H
        #define STDLIB_H
        int abs (int number); //etc.
      #endif
    //#include "mpi.h"
      #ifndef MPI_H
        #define MPI_H
        void MPI_Func(void);
      #endif
    void bar(void);
#endif

void main(int argc, char *argv[]) {
    foo();
    MPI_Func();
    bar();
    exit(0); // Added missing semicolon
}

浏览该代码并注意何时执行各种定义。结果是:

#define STDLIB_H
int abs (int number); //etc.
#define STDLIB_H
#define NULL 0 //etc.
#define MPI_H
void MPI_Func(void);
#define FOO_H
void foo(void);
#define BAR_H
void bar(void);

关于您对其他批评/指针的请求,为什么您在所有标题中都#include stdlib.h 和 mpi.h?我知道这是一个精简的例子,但一般来说,头文件应该只包含声明其内容所必需的文件。如果您使用 stdlib 中的函数或在 foo.c 或 bar.c 中调用 MPI_func(),但函数声明很简单void foo(void),则不应将这些文件包含在头函数中。例如,考虑以下模块:

富.h:

#ifndef FOO_H
#define FOO_H
void foo(void);
#endif

富.c:

#include <stdlib.h>  // Defines type size_t
#include "mpi.h"     // Declares function MPI_func()

#include "foo.h"     // Include self so type definitions and function declarations
                     // in foo.h are available to all functions in foo.c

void foo(void);
  size_t length;
  char msg[] = "Message";

  MPI_func(msg, length);
}

在这个例子中,实现foo()需要来自 stdlib 和 mpi 的东西,但定义不需要。如果 foo() 返回或需要一个size_t值(stdlib 中的 typedef'ed),您需要在 .h 文件中 #include stdlib。

于 2012-07-18T18:47:33.360 回答
1

大多数情况下没有,有一点“是”。您的头文件将被多次“读取”,但在第二次及以后的时间,预处理器将切断所有内容。这意味着它不会浪费编译器的时间,并且块#include内的 s#ifdef只会执行一次(每个头文件)。

这是一个很好的做法。我自己,我还在 s 之前添加了以下行#ifdef

#pragma once

当特定编译器支持时,它保证文件实际上只被读取一次。我认为这种方式更优化一些。

所以,总结一下:

  1. 像您使用的标头保护可以防止编译器多次解释标头内容,但可能会导致预处理器多次解析它(这不是一个大问题),
  2. #pragma once导致特定的头文件只被读取一次。

两者同时使用时,#pragma once如果编译器支持,则应该生效;如果没有,则将应用标头守卫。

于 2012-07-18T17:34:54.090 回答
0

1)好:你有一个“包括警卫”。“stdlib.h”、“mpi.h”和“void foo()”只有在你一次#include“foo.h”时才会被编译器看到

/* foo.h */
#ifndef FOO_H
#define FOO_H

#include <stdlib.h>
#include "mpi.h"

void foo();

#endif

2) BAD:每次你使用它都会#include "foo.h"的全部内容:

/* foo.h */
#include <stdlib.h>
#include "mpi.h"

void foo();

3) 通过#include”,我的意思是“每个编译单元一次”(即相同的.c 源文件)。

这主要是“保护”一个标题(foo.h)调用另一个可能递归调用第一个标题的标题(“bar.h)。

#includes foo.h 的每个不同的编译单元总是会得到“stdlib.h”、“mpi.h”和“void foo()”。关键是它们只会被看到一次 - 而不是在同一个编译单元中多次出现。

4)这都是“编译时”。它与库(即“链接时间”)无关。

于 2012-07-18T17:35:28.417 回答
0

是的,mpi.h将被多次包含(如将stdlib.h);如果mpi.hhas 包括类似于foo.hand的守卫bar.h,那么这将不是问题。

于 2012-07-18T17:35:50.103 回答