当我使用这样的头文件时,如何停止#include冗余头?

use*_*326 3 c compiler-construction

所以我仍然习惯于模块化编程,并希望确保我坚持最佳实践.如果我有下面的两个模块头文件,是否会#included多次包含每个文件的标题(例如"mpi.h")?有没有正确的方法来解释这个?

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

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

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

void foo();

#endif
Run Code Online (Sandbox Code Playgroud)

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

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

void bar();

#endif
Run Code Online (Sandbox Code Playgroud)

并使用示例程序:

/* 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)
}
Run Code Online (Sandbox Code Playgroud)

Kev*_*eer 5

'包含'是什么意思?预处理程序语句#include file复制内容file并用这些内容替换该语句.这无论如何都会发生

如果通过'include'表示"这些文件中的语句和符号将被多次解析而导致警告和错误",那么不,包含警卫会阻止这种情况.

如果通过'include'表示"编译器的某些部分将读取这些文件的某些部分",那么是的,它们将被包含多次.预处理器将读取文件的第二个包含并用空行替换它,因为包含保护,这会产生很小的开销(文件已经在内存中).然而,现代编译器(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
Run Code Online (Sandbox Code Playgroud)

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

在你的例子中,你有一些(但不是很多)更复杂的东西.扩展#includeExampleClient.c中的所有语句将产生以下源:(注意:我缩进了它,但这不是标头的正常样式,预处理器也不会这样做.我只是想让它更具可读性)

/* 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
}
Run Code Online (Sandbox Code Playgroud)

完成该代码并记下执行各种定义的时间.结果是:

#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);
Run Code Online (Sandbox Code Playgroud)

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

foo.h中:

#ifndef FOO_H
#define FOO_H
void foo(void);
#endif
Run Code Online (Sandbox Code Playgroud)

foo.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);
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,执行foo()需要来自stdlib和mpi的东西,但定义没有.如果foo()返回或需要一个size_t值(在stdlib中为typedef'),则需要在.h文件中#include stdlib.