C Language
ヘッダファイルの作成とインクルード
サーチ…
前書き
最近のCでは、ヘッダーファイルが正しく設計され、正しく使用されなければならない重要なツールです。コンパイラは、プログラムの独立してコンパイルされた部分をクロスチェックすることができます。
ヘッダは、一連の施設の消費者が必要とするタイプ、関数、マクロなどを宣言します。これらの機能を使用するすべてのコードにはヘッダーが含まれています。これらの施設を定義するすべてのコードにヘッダーが含まれています。これにより、コンパイラは使用法と定義が一致するかどうかを確認できます。
前書き
Cプロジェクトでヘッダファイルを作成して使用する際には、いくつかのガイドラインがあります。
異端感情
ヘッダーファイルが翻訳単位(TU)に複数回含まれている場合は、ビルドを中断してはなりません。
自己封じ込め
ヘッダーファイルで宣言された機能が必要な場合は、他のヘッダーを明示的に含める必要はありません。
最小
ビルドに失敗することなくヘッダから情報を削除することはできません。
あなたが使用するものを含める(IWYU)
CよりもC ++への関心が高いが、Cでも重要である。 TU内のコード(
code.c
と呼ぶ)がヘッダ("headerA.h"
と呼ぶ)によって宣言された機能を直接使用する場合、TUに含まれていてもcode.c
は#include "headerA.h"
直接#include "headerA.h"
必要があります現時点では"headerA.h"
を含む別のヘッダ("headerB.h"
と呼ぶ)を"headerA.h"
。
場合によっては、これらのガイドラインの1つ以上を破るのに十分な理由があるかもしれませんが、ルールを破っていて、それを破る前にその結果を知っていることに気づくべきです。
偶然性
特定のヘッダーファイルが翻訳単位(TU)に複数回含まれている場合、コンパイルの問題はないはずです。これは「冪等分」と呼ばれます。あなたのヘッダーは冪等でなければなりません。 #include <stdio.h>
が一度だけ含まれていることを保証しなければならない場合、どのくらい難しい人生になるか考えてみてください。
冪等位を実現するには、ヘッダーガードと#pragma once
という2つの方法があります。
ヘッダーガード
ヘッダーガードはシンプルで信頼性が高く、C標準に準拠しています。ヘッダーファイルの最初の非コメント行は、次の形式である必要があります。
#ifndef UNIQUE_ID_FOR_HEADER
#define UNIQUE_ID_FOR_HEADER
最後の非コメント行は、 #endif
あります。
#endif /* UNIQUE_ID_FOR_HEADER */
他の#include
指令を含むすべての操作コードは、これらの行の間にある必要があります。
それぞれの名前は一意でなければなりません。多くの場合、 HEADER_H_INCLUDED
などの名前体系が使用されます。古いコードの中には、ヘッダガードとして定義されているシンボル(例: <stdio.h>
#ifndef BUFSIZ
)を使用していますが、一意の名前ほど信頼性がありません。
1つのオプションは、ヘッダガード名に生成されたMD5(または他の)ハッシュを使用することです。実装に予約されている名前を頻繁に使用するシステムヘッダーで使用されているスキームをエミュレートしないでください。アンダースコアで始まる名前に続いて、別のアンダースコアまたは大文字が続きます。
#pragma once
指令
代わりに、 #pragma once
ディレクティブをサポートするコンパイラもあり#pragma once
が、これはヘッダガードの3行と同じ効果があります。
#pragma once
#pragma once
をサポートするコンパイラには、MS Visual StudioとGCCとClangが含まれています。しかし、移植性が懸念される場合は、ヘッダーガードを使用するか、両方を使用する方がよいでしょう。現代のコンパイラ(C89以降をサポートするコンパイラ)は、知らないプラグマを無視する必要があります( '実装によって認識されないプラグマは無視されます')が、GCCの古いバージョンはそんなに甘やかされませんでした。
自己封じ込め
現代のヘッダーは自己完結型でなければなりません。つまり、 header.h
定義された機能を使用する必要のあるプログラムには、そのヘッダー( #include "header.h"
)を含めることができ、他のヘッダーを最初に含めなければならないかどうかは心配しません。
推奨事項:ヘッダーファイルは自己完結型でなければなりません。
歴史的ルール
歴史的に、これは軽度に論争の対象です。
AT&T Indian Hill Cのスタイルとコーディング基準では、
ヘッダーファイルはネストしないでください。したがって、ヘッダーファイルのプロローグは、ヘッダーが機能するために他のヘッダーが
#include
する必要があるものを記述する必要があります。極端な場合には、多数のヘッダファイルを複数の異なるソースファイルに含める場合には、すべての共通#include
を1つのインクルードファイルに入れることが許容されます。
これは自己封じ込めの対立です。
現代のルール
しかし、それ以来、意見は反対の方向に向いていた。ソースファイルがヘッダheader.h
によって宣言された機能を使用する必要がある場合、プログラマは以下を書くことができるはずです:
#include "header.h"
(コマンド行に正しい検索パスが設定されている場合のみ)、ヘッダーがソースファイルに追加さheader.h
ことなくheader.h
に必要な前提ヘッダーがすべて含まれます。
これにより、ソースコードのモジュール性が向上します。また、コードが変更され、10年または2年にわたってハッキングされた後に発生する「このヘッダーが追加された理由」の憶測からソースを保護します。
NASAのゴダード宇宙飛行センター(GSFC)は、Cのためのコーディング標準は、より現代的な基準の一つですが、追跡するのが少し難しいです。ヘッダーは自己完結型でなければならないと書かれています。また、ヘッダーが自己完結型であることを確認する簡単な方法も提供されます。ヘッダーの実装ファイルにはヘッダーが最初のヘッダーとして含める必要があります。自己完結型でない場合、そのコードはコンパイルされません。
GSFCが提示した根拠は次のとおりです。
§2.1.1ヘッダーは論理的根拠を含む
この規格では、ユニットヘッダが必要とする他の全てのヘッダのための
#include
文をユニットのヘッダに含める必要があります。ユニットヘッダに#include
を最初に配置すると、コンパイラはヘッダに必要なすべての#include
文が含まれていることを確認できます。
この標準では許可されていない別の設計では、ヘッダーに
#include
文を使用することはできません。すべての#includes
は本体ファイルで行われます。ユニットヘッダーファイルには、必要なヘッダーが適切な順序で含まれていることを確認する#ifdefステートメントが含まれている必要があります。
代替設計の利点の1つは、本体ファイルの
#include
リストがmakefileに必要な依存関係リストであり、このリストがコンパイラによってチェックされていることです。標準設計では、ツールを使用して依存関係リストを生成する必要があります。しかし、ブランチ推奨の開発環境はすべて、このようなツールを提供します。
代替設計の主な欠点は、ユニットの必須ヘッダーリストが変更された場合、そのユニットを使用する各ファイルを編集して、
#include
ステートメントリストを更新する必要があることです。また、コンパイラライブラリユニットに必要なヘッダリストは、異なるターゲットで異なる場合があります。
別の設計のもう一つの欠点は、コンパイラライブラリのヘッダファイルや他のサードパーティのファイルを変更して、必要な
#ifdef
文を追加する必要があることです。
したがって、自己封じ込めとは、
- ヘッダー場合
header.h
新しいネストされたヘッダの必要extra.h
、あなたが使用するすべてのソースファイルをチェックする必要はありませんheader.h
追加する必要があるかどうかを確認するためにextra.h
。 - ヘッダー場合
header.h
もはや特定のヘッダー含める必要がnotneeded.h
、あなたが使用するすべてのソースファイルをチェックする必要はありませんheader.h
あなたは安全に取り外すことができるかどうか確認するためにnotneeded.h
(ただし、参照、あなたが使用しているものを含めるを 。 - 前提条件のヘッダーを含めるための正しい順序を確立する必要はありません(ジョブを適切に実行するためにトポロジカルな並べ替えが必要です)。
自己封じ込めの確認
chkhdr
性とヘッダーファイルの自己包含をテストするために使用できるスクリプトchkhdr
静的ライブラリとのリンクを参照してください。
最小
ヘッダーは重要な一貫性チェックメカニズムですが、可能な限り小さくする必要があります。特に、実装ファイルには他のヘッダーが必要なため、ヘッダーに他のヘッダーを含めないでください。ヘッダーには、説明されているサービスのコンシューマーにとって必要なヘッダーのみが含まれている必要があります。
たとえば、関数ヘッダの1つがFILE *
型(または<stdio.h>
のみで定義された他の型の1つ)を使用しない限り、プロジェクトヘッダには<stdio.h>
含めないでください。インタフェースがsize_t
使用する場合、十分な最小のヘッダーは<stddef.h>
です。明らかに、 size_t
を定義する別のヘッダーが含まれている場合、 <stddef.h>
も含める必要はありません。
ヘッダーが最小の場合、コンパイル時間も最小限に抑えられます。
他の多くのヘッダーを含めることを唯一の目的とするヘッダーを考案することは可能です。すべてのヘッダーで説明されているすべての機能が実際に必要なソースファイルはほとんどないため、これらはほとんど目に見えません。例えば、ヘッダが常に存在するとは限らないので、標準Cヘッダをすべて含む<standard-ch>
を考案することができます。しかし、 <locale.h>
または<tgmath.h>
の機能を実際に使用するプログラムはごくわずかです。
- Cで複数の実装ファイルをリンクする方法もご覧ください。
あなたが使用するものを含める(IWYU)
Googleのプロジェクト使用のインクルード (IWYU)は、ソースファイルにコードで使用されているすべてのヘッダーが含まれるようにします。
ソースファイルsource.c
に、 freeloader.h
同時に含むヘッダarbitrary.h
が含まれていると仮定しますが、ソースファイルもfreeloader.h
の機能を明示的かつ独立して使用します。すべてがうまく始まります。その後、ある日arbitrary.h
が変更され、クライアントはもはやfreeloader.h
の機能を必要としなくなります。突然、 source.c
コンパイルを中止-それはIWYU基準を満たしていなかったので。内のコードなのでsource.c
明示的に施設使用freeloader.h
明示がなされている必要があります- 、それが使用するものが含まれている必要がある#include "freeloader.h"
あまりにもソースインチ( 偶数性は問題がないことを保証したはずです)。
IWYUの哲学は、インターフェイスに合理的な変更を加えてもコードがコンパイルされ続ける可能性を最大にします。明らかに、あなたのコードが公開されたインターフェイスから後で削除される関数を呼び出す場合、準備の量は変更が必要になるのを防ぐことができません。このため、可能な場合はAPIへの変更を避け、複数のリリースなどでは廃止予定サイクルがある理由です。
これは標準的なヘッダーが互いに含めることができるので、C ++の特有の問題です。ソースファイルfile.cpp
は、あるプラットフォーム上に別のヘッダheader2.h
含む1つのヘッダheader1.h
を含むことができる。 file.cpp
の施設使用が判明するかもしれないheader2.h
同様。これは最初は問題ではないでしょう - header1.h
はheader2.h
含まれているため、コードはコンパイルされheader2.h
。別のプラットフォーム、または現在のプラットフォームのアップグレードでは、 header1.h
それはもはや含まれて改訂することはできなかったheader2.h
し、その後file.cpp
、結果として、コンパイルを停止するでしょう。
IWYUは問題をheader2.h
、 file.cpp
に直接header2.h
ことを推奨します。これによりコンパイルが継続されます。 Cコードにも同様の考慮事項が適用されます。
表記法およびその他
Cの標準では、 #include <header.h>
と#include "header.h"
表記にはほとんど違いがないと言われています。
[
#include <header.h>
]は、<
と>
区切り文字の間で指定されたシーケンスによって一意に識別されるヘッダーに対して、実装定義の場所のシーケンスを検索し、ヘッダーの内容全体でそのディレクティブを置き換えます。どのように場所が指定されるか、またはヘッダーが識別されるかは実装定義です。
[
#include "header.h"
]は、指定されたシーケンスによって識別されるソースファイルの内容全体が"…"
デリミタの間で置き換えられます。指定されたソースファイルは、実装定義の方法で検索されます。この検索がサポートされていない場合、または検索が失敗した場合、ディレクティブは[#include <header.h>
]と同じように再処理されます。
したがって、二重引用符で囲まれたフォームは、角カッコで囲まれたフォームよりも多くの場所で表示されることがあります。標準では、二重引用符を使用する場合はコンパイルが機能しますが、標準のヘッダーを角かっこに含めるように指定しています。同様に、POSIXなどの標準では角括弧付きの書式を使用しています。プロジェクトで定義されたヘッダーの二重引用符ヘッダーを予約します。外部定義ヘッダー(プロジェクトが依存する他のプロジェクトのヘッダーを含む)では、角括弧表記が最適です。
コンパイラはスペースを受け付けなくても、 #include
とヘッダの間にスペースが必要であることに注意してください。スペースは安いです。
多くのプロジェクトでは、次のような表記法が使用されています。
#include <openssl/ssl.h>
#include <sys/stat.h>
#include <linux/kernel.h>
プロジェクトでその名前空間コントロールを使用するかどうかを検討する必要があります(おそらくよい考えです)。既存のプロジェクトで使用されている名前を明確にする必要があります(特に、 sys
とlinux
両方が悪い選択肢になります)。
これを使用する場合は、コードを注意深く使用し、表記の使用を一貫させる必要があります。
#include "../include/header.h"
表記を使用しないでください。
ヘッダーファイルは、変数を定義することはめったにありません。グローバル変数は最小限に抑えますが、グローバル変数が必要な場合はヘッダーに宣言し、適切なソースファイルでその変数を定義します。ソースファイルには宣言と定義をクロスチェックするヘッダーが含まれます変数を使用するすべてのソースファイルはヘッダを使用して宣言します。
結果:ソースファイルにグローバル変数を宣言しません。ソースファイルには定義のみが含まれます。
ヘッダーファイルはstatic
関数を宣言すべきではありません。 static inline
関数は、複数のソースファイルで関数が必要な場合にヘッダーで定義されます。
- ソースファイルは、グローバル変数とグローバル関数を定義します。
- ソースファイルはグローバル変数や関数の存在を宣言しません。変数または関数を宣言するヘッダーが含まれます。
- ヘッダーファイルは、グローバル変数と関数(および型とその他のサポート資料)を宣言します。
- ヘッダーファイルでは、変数または
static
inline
関数以外の関数は定義されません。
クロスリファレンス
- Cで関数を文書化する場所は?
- CおよびC ++での標準ヘッダファイルのリスト
- C99で
static
またはextern
なしでinline
で使えますか? -
extern
を使用してソースファイル間で変数を共有するにはどうすればよいですか? - ヘッダに
"../include/header.h"
ような相対パスのメリットはありますか? - ヘッダ包含の最適化
- すべてのヘッダーを含める必要がありますか?