3

そのため、私はまだモジュラー プログラミングに慣れていないので、ベスト プラクティスを確実に順守したいと考えています。以下の 2 つのモジュール ヘッダー ファイルがある場合、#included各ファイル ("mpi.h" など) ごとのヘッダーは複数回含まれますか? これを説明する適切な方法はありますか?

また、私のモジュールヘッダーは通常、これらの例のように見えるため、他の批判/ポインターが役立つ場合があります.

/* 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)
}
4

4 に答える 4

5

「含める」とはどういう意味ですか? プリプロセッサ ステートメントは、ステートメント#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 する必要があります。

于 2012-07-18T18:47:33.360 に答える
1

ほとんどの場合、「はい」と少しだけ「いいえ」です。ヘッダー ファイルは 2 回以上「読み取られ」ますが、2 回目以降はプリプロセッサがすべての内容を切り捨てます。これは、コンパイラの時間を無駄にせず#include、ブロック内で s が#ifdef(ヘッダー ファイルごとに) 1 回だけ実行されることを意味します。

それは良い習慣です。私自身、#ifdefs の前に次の行も追加します。

#pragma once

特定のコンパイラでサポートされている場合、ファイルが実際に一度だけ読み取られることが保証されます。そういう意味ではもう少し最適だと思います。

要約すると、次のようになります。

  1. あなたが使用しているようなヘッダーガードは、コンパイラーがヘッダーの内容を複数回解釈するのを防ぎますが、プリプロセッサーがそれを複数回解析する可能性があります (これは大きな問題ではありません)。
  2. #pragma once特定のヘッダー ファイルが 1 回だけ読み取られるようにします。

両方を使用する#pragma once場合、コンパイラでサポートされている場合は有効にする必要があります。そうでない場合は、ヘッダー ガードが適用されます。

于 2012-07-18T17:34:54.090 に答える
0

1) 良い: 「インクルード ガード」があります。「stdlib.h」、「mpi.h」、および「void foo()」は、「foo.h」を初めて#includeしたときにのみコンパイラーによって認識されます。

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

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

void foo();

#endif

2) 悪い: これは、使用するたびに "foo.h" の内容全体を #include します:

/* foo.h */
#include <stdlib.h>
#include "mpi.h"

void foo();

3) #include" とは、"コンパイル ユニットごとに 1 回" (つまり、同じ.c ソース ファイル) という意味です。

これは主に、最初のヘッダーを再帰的に呼び出す可能性のある別のヘッダー (「bar.h」) を呼び出すヘッダー (foo.h) から「保護」します。

foo.h を #include するすべての異なるコンパイル ユニットは、常に "stdlib.h"、"mpi.h"、および "void foo()" を取得します。ポイントは、同じコンパイル単位で複数回表示されるのではなく、一度だけ表示されることです。

4) これはすべて「コンパイル時」です。ライブラリ(「リンク時」)とは関係ありません。

于 2012-07-18T17:35:28.417 に答える
0

はい、mpi.h複数回含まれます (同様にstdlib.h)。とmpi.hに似たガードが含まれている場合、問題にはなりません。 foo.hbar.h

于 2012-07-18T17:35:50.103 に答える