web-dev-qa-db-ja.com

ヘッダーファイルはプログラム全体で1回だけ含まれていますか?

これはよくある質問ですが、完全に理解することはできません。

[〜#〜] c [〜#〜]または複数の異なるソースおよびヘッダーファイルから生成されたC++プログラムでは、ヘッダーガードが使用されている場合、各ヘッダーファイルはコード全体に1回だけ含まれます。 ?

ヘッダーファイル(インクルードガードを含む)は1つの翻訳単位に1回だけ含まれるが、コード全体では複数回含まれると誰かが以前に言った。これは本当ですか?

コード全体で1回だけ含まれる場合、1つのファイルがそのファイルを含めようとしていて、プリプロセッサがそのファイルが既に含まれていることを検出すると、それを使用したいファイルは、以前に含まれていたコード内の場所をどのようにして知るのでしょうか。

28
Engineer999

これはプロセスです:

source           header   source header header
   \           /        \   |      /   /
    \         /          \  |     /   /
  PREPROCESSOR            PREPROCESSOR
       |                      |
       V                      V
 preprocessed code      preprocessed code
       |                      |
    COMPILER               COMPILER
       |                      |
       V                      V
  object code              object code
             \            /
              \          /
               \        /
                 LINKER
                   | 
                   V
               executable

前処理

#includeはこの最初のステップです。指定したファイルを処理し、結果を出力に挿入するようにプリプロセッサに指示します。

ABCが含まれ、BCが含まれている場合、プリプロセッサのAの出力にはCのテキストを2回処理しました。

宣言が重複するため、これは問題です。解決策は、プリプロセッサ変数を使用して、ソースコードが含まれているかどうか(別名ヘッダーガード)を追跡することです。

#ifndef EXAMPLE_H
#define EXAMPLE_H

// header contents

#endif

初めて、 EXAMPLE_Hは未定義であり、プリプロセッサはifndef/endifブロック内のコンテンツを評価します。 2回目は、そのブロックをスキップします。したがって、処理された出力は変更されます。定義は1回だけ含まれます。

これは非常に一般的であり、一部のコンパイラーには非標準のディレクティブが実装されており、短いため、固有のプリプロセッサー変数を選択する必要がありません。

#pragma once

// header contents

C/C++コードの移植性と、使用するヘッダーガードを把握できます。

ヘッダーガードは、各ヘッダーファイルの内容が翻訳単位の前処理されたコードに最大1回存在することを保証します。

コンパイル

コンパイラーは、前処理されたC/C++からマシンコードを生成します。

通常、ヘッダーファイルには宣言のみが含まれ、実際の定義(実装)は含まれません。コンパイラーには、現在定義が欠落しているもののシンボルテーブルが含まれています。

リンク

リンカはオブジェクトファイルを結合します。これは、定義(別名実装)をシンボルテーブルへの参照と照合します。

2つのオブジェクトファイルが定義を提供し、リンカが1つを取得する場合があります。これは、ヘッダーに実行可能コードを配置した場合に発生します。これは一般にCでは発生しませんが、C++ではテンプレートが原因で非常に頻繁に発生します。

ヘッダーの「コード」は、宣言か定義かに関係なく、すべてのオブジェクトファイルに複数回含まれますが、リンカはそれらすべてを一緒にマージするため、実行可能。(複数回存在するインライン関数を除外しています。)

29
Paul Draper

「ヘッダーファイル」は、コンパイルの開始前にプリプロセッサによって実際に挿入されます。 #includeディレクティブを単に「置き換える」と考えてください。

ガード...

#ifndef MY_HEADER_H
#define MY_HEADER_H

....

#endif

...置換後に実行されます。そのため、ヘッダーは実際には複数回含まれる可能性がありますが、テキストの「保護された」部分は、プリプロセッサーによってコンパイラーに1回だけ渡されます。

したがって、ヘッダーにコード生成定義がある場合、それらは-もちろん-コンパイルユニットのオブジェクトファイルに含まれます(別名「モジュール」)。同じヘッダーが複数のモジュールで#includededである場合、これらは複数回表示されます。

static定義の場合、これはモジュール(別名fileスコープ)を超えて表示されないため、これはまったく問題ありません。プログラム全体の定義の場合、これは異なり、「複数の定義」エラーが発生します。

注:これは主に[〜#〜] c [〜#〜]用です。 C++の場合、クラスなどにより、何が/複数のグローバルオブジェクトが許可されるかがさらに複雑になるため、大きな違いがあります。

適切な include guards を含むヘッダーファイルは、翻訳単位ごとに1回だけ含まれます。厳密に言うと、includedが複数回含まれる場合がありますが、プリプロセッサ間の部分は#ifndefおよび#endifは、後続の包含でスキップされます。正しく行われている場合、これはファイルのすべて(またはほとんど)です。

翻訳単位は通常「ソースファイル」に対応しますが、一部のあいまいな実装では異なる定義が使用される場合があります。個別にコンパイルされたソースファイルに同じヘッダーが含まれている場合、プリプロセッサは、別のファイルがすでにそれをインクルードしたこと、または他のファイルが同じプロジェクトの一部であることを知ることができません

複数のソースファイル(翻訳単位)を1つのバイナリにリンクする際、ヘッダーが複数の定義で問題が発生する場合があることに注意してください。宣言、テンプレート、inlineとマークされた関数定義、または静的変数定義のみで構成されます。これを回避するには、ヘッダーで関数を宣言し、他のソースファイルとリンクする別のソースファイルでそれらを定義する必要があります。

7
Andrew

ヘッダーファイルは、翻訳単位ごとに1回含まれます。各翻訳単位はコンパイルプロセスで個別に処理されるため、プログラムごとに複数回含めることができます。それらは、リンクプロセス中にまとめられ、完全なプログラムを形成します。

5
Bryan Shaw