「含める」とはどういう意味ですか? プリプロセッサ ステートメントは、ステートメント#include file
の内容をコピーし、file
これらの内容でステートメントを置き換えます。これは関係なく起こります
「インクルード」が「これらのファイル内のステートメントとシンボルが複数回解析され、警告とエラーが発生する」ことを意味する場合は、いいえ、インクルード ガードがそれを防ぎます。
「含める」とは、「コンパイラの一部がこれらのファイルの一部を読み取る」ことを意味する場合、はい、それらは複数回含まれます。プリプロセッサはファイルの 2 番目のインクルードを読み取り、それを空白行に置き換えます。これはインクルード ガードが原因で、わずかなオーバーヘッドが発生します (ファイルは既にメモリ内にあります)。ただし、最新のコンパイラ (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
したがって、2 番目は false を返すと結論付けることができるため#ifndef
、プリプロセッサの最初のパスの後、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);
他の批判/ポインタのリクエストに関して、なぜすべてのヘッダーに stdlib.h と mpi.h を #include するのですか? これは簡略化された例であることは理解していますが、一般に、ヘッダー ファイルには、その内容の宣言に必要なファイルのみを含める必要があります。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() が値 (stdlib で typedef されている) を返すか必要とするsize_t
場合は、.h ファイルに stdlib を #include する必要があります。