How can I stop #including redundant headers when I use header files like these?
所以我仍然习惯于模块化编程,并希望确保我坚持最佳实践。如果下面有两个模块头文件,每个文件(例如"mpi.h")的头文件
另外,我的模块头通常看起来像这些例子,所以任何其他的批评/指针都是有用的。
1 2 3 4 5 6 7 8 9 10 | /* foo.h */ #ifndef FOO_H #define FOO_H #include <stdlib.h> #include"mpi.h" void foo(); #endif |
和
1 2 3 4 5 6 7 8 9 10 | /* bar.h */ #ifndef BAR_H #define BAR_H #include <stdlib.h> #include"mpi.h" void bar(); #endif |
并使用示例程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 | /* 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) } |
"包括"是什么意思?预处理器语句
如果"include"的意思是"这些文件中的语句和符号将被多次解析,从而导致警告和错误",那么"include"保护将阻止这种情况发生。
如果"include"的意思是"编译器的某些部分将读取这些文件的某些部分",那么是的,它们将被包含多次。预处理器将读取文件的第二个包含内容,并将其替换为空行,因为包含保护会产生很小的开销(文件已经在内存中)。但是,现代编译器(gcc,不确定其他编译器)可能会进行优化以避免出现这种情况,并注意到该文件在第一次传递时包含了保护,只需丢弃将来的包含内容,消除开销-不要担心这里的速度,清晰性和模块性更为重要。当然,编译是一个耗时的过程,但是
为了更好地理解包括防护装置,请考虑以下代码示例:
1 2 3 4 5 6 7 8 9 10 11 | #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 |
在(第一次)预处理之后,将如何定义
在您的示例中,您得到了稍微复杂一些(但不太复杂)的内容。扩展exampleclient.c中的所有EDOCX1[2]语句将导致以下源:(注意:我缩进了它,但这不是头的正常样式,预处理器不会这样做。我只是想让它更可读)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | /* 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 } |
在执行各种定义时,请仔细阅读该代码并注意。结果是:
1 2 3 4 5 6 7 8 9 10 | #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); |
关于您对其他批评/指针的请求,为什么在所有的头中都包含stdlib.h和mpi.h?我理解这是一个简化的示例,但一般来说,头文件应该只包含声明其内容所必需的文件。如果使用stdlib中的函数或在foo.c或bar.c中调用mpi_func(),但函数声明只是
福:
1 2 3 4 | #ifndef FOO_H #define FOO_H void foo(void); #endif |
Fo.C:
1 2 3 4 5 6 7 8 9 10 11 12 | #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); } |
在本例中,
大多数情况下没有,只是有点"是"。您的头文件将被"读取"多次,但在第二次和以后的时间,预处理器将切断所有内容。这意味着它不会浪费编译器的时间,而且
这是一个很好的练习。我自己也在
1 | #pragma once |
当特定编译器支持时,它保证文件实际上只读取一次。我觉得这样比较理想。
所以,总结一下:
同时使用时,如果编译器支持,
是的,
1)好:你有一个"包括警卫"。stdlib.h、"mpi.h"和"void foo()"只有在首次包含"foo.h"时才会被编译器看到。
1 2 3 4 5 6 7 8 9 10 | /* foo.h */ #ifndef FOO_H #define FOO_H #include <stdlib.h> #include"mpi.h" void foo(); #endif |
2)坏:每次使用"foo.h"时,它都会包含"foo.h"的全部内容:
1 2 3 4 5 | /* foo.h */ #include <stdlib.h> #include"mpi.h" void foo(); |
3)通过include,我的意思是"每个编译单元一次"(即相同的.c源文件)。
这主要是针对一个头(foo.h)调用另一个头("bar.h")进行"保护",该头可能递归调用第一个头。
包括foo.h的每个不同编译单元都将始终获得"stdlib.h"、"mpi.h"和"void foo()"。关键是它们只能被看到一次,而不是在同一个编译单元中被多次看到。
4)这都是"编译时"。它与库(即"链接时间")无关。