所以我仍然习惯于模块化编程,并希望确保我遵守最佳实践。如果我有下面的两个模块头文件,那么每个文件(例如“mpi.h”)的标题#included
是否会被多次包含?是否有适当的方法来解释这个问题?
此外,我的模块标题通常看起来像这些示例,因此任何其他批评/指针都会有所帮助。
/* 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)
}
答案 0 :(得分:5)
'include'是什么意思?预处理程序语句#include file
复制file
的内容,并用这些内容替换该语句。无论如何都会发生这种情况
如果通过'include'表示“这些文件中的语句和符号将被多次解析而导致警告和错误”,那么不,包含警卫会阻止它。
如果通过'include'表示“编译器的某些部分将读取这些文件的某些部分”,那么是的,它们将被多次包含。预处理器将读取文件的第二个包含并用空行替换它,因为包含保护,这会产生很小的开销(文件已经在内存中)。然而,现代编译器(海湾合作委员会,不确定其他人)可能会进行优化以避免这种情况,并注意到该文件在第一次通过时包含警卫并简单地丢弃未来的内容,消除开销 - 不要担心速度,清晰度和模块化更重要。编译是一个耗时的过程,当然,#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)
,则不应在头函数中包含这些文件。例如,请考虑以下模块:
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);
}
在这个例子中,foo()
的实现需要来自stdlib和mpi的东西,但定义却没有。如果foo()返回或需要size_t
值(stdlib中的typedef'),则需要在.h文件中#include stdlib。
答案 1 :(得分:1)
大多数情况下没有,有点'是'。您的头文件将被多次“读取”,但在第二次及以后,预处理器将切断所有内容。这意味着它不会浪费编译器的时间,#include
块中的#ifdef
只会被执行一次(每个头文件)。
这是一个很好的做法。我自己,我还在#ifdef
s之前添加了以下行:
#pragma once
当特定编译器支持时,它保证文件实际上只读取一次。我认为这种方式更加优化。
所以,总结一下:
#pragma once
导致特定的头文件只能读取一次。当使用两者时,如果编译器支持,#pragma once
应该有效;如果没有,将使用标题保护。
答案 2 :(得分:0)
1)好的:你有"include guard"。 “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)坏:这将#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)这都是“编译时”。它与库无关(即“链接时间”)。
答案 3 :(得分:0)
是的,mpi.h
将被多次包含(stdlib.h
也是如此);如果mpi.h
包含类似于foo.h
和bar.h
的警卫,则不会出现问题。