C 当我使用这样的头文件时,如何停止包含冗余头文件?

C 当我使用这样的头文件时,如何停止包含冗余头文件?,c,compiler-construction,C,Compiler Construction,因此,我仍然习惯于模块化编程,并希望确保遵循最佳实践。如果我有下面的两个模块头文件,每个文件(例如“mpi.h”)包含的头文件#是否会被多次包含?有没有合适的方法来解释这一点 此外,我的模块标题通常与这些示例类似,因此任何其他批评/指点都会有所帮助 /* foo.h */ #ifndef FOO_H #define FOO_H #include <stdlib.h> #include "mpi.h" void foo(); #endif /*foo.h*/ #伊夫德夫·福安

因此,我仍然习惯于模块化编程,并希望确保遵循最佳实践。如果我有下面的两个模块头文件,每个文件(例如“mpi.h”)包含的头文件
#是否会被多次包含?有没有合适的方法来解释这一点

此外,我的模块标题通常与这些示例类似,因此任何其他批评/指点都会有所帮助

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

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

void foo();

#endif
/*foo.h*/
#伊夫德夫·福安
#定义FOO_H
#包括
#包括“mpi.h”
void foo();
#恩迪夫

/*bar.h*/
#ifndef BAR_H
#定义条形图
#包括
#包括“mpi.h”
空心钢筋();
#恩迪夫
并使用示例程序:

/* 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)
}
/*ExampleClient.c*/
#包括
#包括
#包括“mpi.h”
#包括“foo.h”
#包括“bar.h”
void main(int argc,char*argv[]){
foo();
MPI_Func();
bar();
出口(0)
}

基本上是否定的,还有一点“是”。您的头文件将被多次“读取”,但在第二次或以后,预处理器将切断所有内容。这意味着它不会浪费编译器的时间,而且
#在
#中包含
#ifdef
块只执行一次(每个头文件)

这是一个很好的做法。我本人也在
#ifdef
s之前添加了以下行:

#pragma once
当特定编译器支持时,它保证文件实际上只读取一次。我认为这样会更好一点

总而言之:

  • 像您正在使用的头保护防止编译器多次解释头内容,但可能会导致预处理器多次解析它(这不是一个大问题)
  • #pragma once
    导致特定头文件只读取一次
  • 当同时使用这两种语言时,如果编译器支持,
    #pragma once
    应该有效;如果没有,将使用收割台防护装置。

    1)好:您有一个。“stdlib.h”、“mpi.h”和“void foo()”只有在第一次包含“foo.h”时才被编译器看到”

    /*foo.h*/
    #伊夫德夫·福安
    #定义FOO_H
    #包括
    #包括“mpi.h”
    void foo();
    #恩迪夫
    
    2) 坏:每次使用“foo.h”时,它都会包含“foo.h”的全部内容:

    /* foo.h */
    #include <stdlib.h>
    #include "mpi.h"
    
    void foo();
    
    /*foo.h*/
    #包括
    #包括“mpi.h”
    void foo();
    
    3) 所谓#include”,我的意思是“每个编译单元一次”(即相同的.c源文件)

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

    #包含foo.h的每个不同编译单元将始终获得“stdlib.h”、“mpi.h”和“void foo()”。关键是它们只会在同一个编译单元中出现一次,而不是多次


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

    是的,
    mpi.h
    将被多次包含(就像
    stdlib.h
    一样);如果
    mpi.h
    包含类似于
    foo.h
    bar.h
    的防护装置,则不会出现问题

    你说的“包括”是什么意思?预处理器语句
    #include 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
    
    在(第一次)预处理之后,
    GUARDED
    将被定义为什么?预处理器语句
    #ifndef
    或其等效语句
    #if!defined()
    将返回
    false
    ,如果它们的参数确实已定义。因此,我们可以得出结论,第二个
    #ifndef
    将返回false,因此在预处理器第一次通过后,只有第一个受保护的定义将保留。在下一次通过时,程序中剩余的
    受保护的
    实例将被1替换

    在您的示例中,您得到了稍微(但不太)复杂的东西。扩展ExampleClient.c中的所有
    #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
    
    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);
    }
    
    #include//定义类型大小
    #包括“mpi.h”//声明函数mpi_func()
    #包括“foo.h”//include self-so类型定义和函数声明
    //在foo.h中,对foo.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
    }
    
    #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);
    
    #ifndef FOO_H
    #define FOO_H
    void foo(void);
    #endif
    
    #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);
    }