#include と C プログラムのヘッダー ファイルを深く理解し、C プロジェクトには .h ファイル (ドッグ ヘッダー) のみが含まれるようにする

序文

(1) 今日、#pragma が一度書かれたヘッダファイルを見たので、最初は少し混乱しました。後で、これがヘッダー ファイルが繰り返しインクルードされるのを防ぐ方法でもあることが判明しました。
(2)それでは二重インクルード防止のためのヘッダファイルについてブログを書こうと思います。書いていて、なぜヘッダファイルの繰り返しインクルードを防ぐ必要があるのか​​を突然考えました。
(3) どうやら、c プロジェクトのコンパイルまで遡ることができました。この記事では、C プログラムの #include とヘッダー ファイルについて詳しく紹介します。また、ヘッダー ファイルが c プロジェクトに繰り返しインクルードされるのを防ぐ 2 つの方法も紹介します。

ヘッダー ファイルが繰り返しインクルードされるのを防ぐ必要があるのはなぜですか

一般的にヘッダー ファイルに含まれる内容

(1) ヘッダー ファイルに含まれる 2 つの書き込み方法を説明する前に、なぜヘッダー ファイルが繰り返し含まれないようにするのかを知る必要があります。
(2) まず、C プロジェクトのヘッダー ファイルに一般的にどの要素が配置されるかを知る必要があります。私の個人的な経験に関する限り、一般的なヘッダー ファイルには 5 つのものしか入れられません。

// 头文件包含
#include  "stm32f10x.h"
// 宏定义
#define PI 3.14159
// 函数声明
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(double a, double b);
//extern申明外部变量
extern int global_variable; // 只是声明,不是定义
// 结构体类型定义
typedef struct Point {
    
    
    int x;
    int y;
} Point;

#include とヘッダー ファイルについての深い理解

実践 1 — 通常のプロジェクト文書の作成

(1) プロジェクト内に多数の c ファイルと h ファイルが存在することは誰もが知っています。C 言語では、c ファイルがロジック コードの記述を担当し、h ファイルが一部の宣言を担当すると規定されています。
(2) C ファイルは h ファイルを通じていくつかの宣言情報を取得します。たとえば、main.c は test.c の add() 関数を取得する必要があります。add をインクルードするには #include "test.h" を使用するだけです。 test.c ()関数。
(3) gcc でコンパイルしたところ、この従来の書き方で問題ないことが分かりました。

/**************  mian.c  **************/   
#include "test.h"
int main()
{
    
    
	add(3,4);
	return 0;
}
/**************  test.h  **************/ 
int add(int a,int b);
/**************  test.c  **************/ 
int add(int a,int b)
{
    
    
	return a+b;
}

ここに画像の説明を挿入

実践 2 - プロジェクト ファイルにはヘッダー ファイルがありません

(1) ここで、.h ファイルを使わずに、main.c に関数宣言を直接記述することを想定して書き方を変更します。
(2) コンパイルに合格し、操作が成功します。それでわかります、プロジェクト ファイルにはヘッダー ファイルは必要ありません。

/**************  mian.c  **************/   
#include "test.h"
int add(int a,int b);
int main()
{
    
    
	add(3,4);
	return 0;
}
/**************  test.c  **************/ 
int add(int a,int b)
{
    
    
	return a+b;
}

ここに画像の説明を挿入

ヘッダーファイルの用途は何ですか

(1) 上記の例から、ヘッダー ファイルがなくてもプロジェクトが正常に実行できることがわかります。では、ヘッダー ファイルは何のために必要なのでしょうか?
(2) 上記コードは比較的小さいため、ヘッダファイルの機能は見えません。大規模なプロジェクトを開発しているとします。その中には多くの関数呼び出しがあるはずです。ヘッダー ファイルがない場合、C ファイルを作成するときに、そのファイルに大量の関数宣言を記述する必要があります。次のように

int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(double a, double b);
int main()
{
    
    
	int a=4,b=6,c;
	c = add(a,b);
	c = subtract(a,b);
	c = multiply(a,b);
	c = divide(a,b);
	return 0;
}

(3) したがって、ヘッダー ファイルの機能は関数宣言を格納することであることがわかります。実を言うと、ヘッダー ファイルは C ファイルのディレクトリですヘッダー ファイルを見るだけで、対応する C ファイルがおそらく何を実装しているかを知ることができます。
(4) しかし、ヘッダー ファイルには通常、関数宣言だけでなく、構造体定義、外部変数の extern 宣言、およびマクロ定義も含まれていることはわかっています。これもディレクトリ情報の一部として理解できます。ヘッダー ファイルを見るだけで、対応する C ファイルの内容を大まかに知ることができます。

ヘッダーファイルの名前付け

(1) ヘッダー ファイルが実際には C ファイルのディレクトリであることはわかっていますが、ヘッダー ファイルに名前を付けることに意味はありますか?
(2) もちろんあります。一般に、main.c にはヘッダー ファイルがありません。主要なビジネス プログラムは main.c で実行されるため、ヘッダー ファイルを構成する必要はありません。
(3) しかし、プログラムの移植性を高めるために、モジュール設計の概念を提唱していることは誰もが知っています。したがって、他のモジュール ファイルごとにヘッダー ファイルを構成する必要があります。このモジュールを取得したら、そのヘッダー ファイルのいくつかを確認するだけです。基本的な実装については、バグが発生したときに検討します。
(4) ヘッダファイルはCファイルを記述するため、ヘッダファイル名とCファイル名は同じでなければならないと規定されています。たとえば、与えられた C ファイルが OLED.c である場合、そのヘッダー ファイルは OLED.h である必要があります。
(5) この時点で、反抗的な若者の中には、OLED.c のヘッダー ファイルに nb.h という名前を付けてもいいですか、と尋ねたい人もいるかもしれません。殴られることを恐れない限り、答えは間違いなく「イエス」です。

#include は何をするのでしょうか?

(1) ヘッダー ファイルが何であるかがわかったので、#include が何をするかを見てみましょう。
(2) gcc -E コマンドを使用すると、プリコンパイルされた C ファイルの結果を確認できます。次の結果から、次のことがわかります。#include の本質は、後でインクルードされるファイルの内容をコピーすることです。
(3) まだ私に何か言ってほしいという人もいるかもしれないが、本当に言うことは何もない。(苦笑) #include はただのコピーですから。

ここに画像の説明を挿入

実践 3 - プロジェクト ファイルに繰り返しインクルードされるヘッダー ファイルがある

(1) ヘッダーファイルと#includeの役割が理解できたので、もう一度展開してみましょう。通常の開発では、ヘッダー ファイルは必ず複数回インクルードされます。stdio.h ファイルを例にとると、このヘッダー ファイルには printf 関数の宣言が含まれているため、ほとんどの C ファイルではヘッダー ファイルのインクルードに #include <stdio.h> を使用する必要があります。
(2) main.c で bh と ah が使用されており、ah に bh が含まれている場合、#include が実際にヘッダー ファイルをコピーしていることが上でわかります。これにより、繰り返し混入の問題が発生します。

ここに画像の説明を挿入

/**************  mian.c  **************/   
#include "a.h"
#include "b.h"
int main() 
{
    
    
    int result = add(three, 4);
    return 0;
}
/**************  a.h  **************/ 
#define three 3
int add(int a, int b);
extern int x;
/*
struct student{
	char* name;
	int age;
	char* sex;
};
*/
/**************  b.h  **************/ 
#include "a.h"

(3) 次に、gcc を使用してコンパイルすると、正常にコンパイルされて実行できることがわかります。結果が表示され、正常に実行することもできます。

ここに画像の説明を挿入

(4) 「なんだ、ヘッダーファイルが繰り返しインクルードされているというエラーレポートが出てくるのではないか?」と疑問に思う人もいるかもしれない。
(5) 残念ながら、いいえ。C ファイルを実行可能ファイルに変換するプロセスを少し理解している人ならわかるでしょう。C ファイルから実行可能ファイルに至るまでには、前処理、コンパイル、アセンブル、リンクという4 つのプロセスを経る必要があり構文検出は再コンパイル中に行われますそれから問題があります、C ファイルが前処理されている場合、最終的な C ファイルは構文に準拠しており、エラーは報告されません。
(6) 次に、前処理後の main.c がどのようになるかを見てみましょう。前処理が実際に 2 つのことを行っていることがわかります。
<1> 3 を数値 3 にします
。 <2> 次に、関数宣言と extern を test.i に 2 回コピーします。
(7) 関数宣言と extern が 2 回繰り返されていますが、C 言語の文法に従って記述しています。したがって、ヘッダー ファイルにマクロ定義、関数宣言、extern のみがある場合は、条件付きコンパイルを行わなくてもエラーは報告されません。
(8) ただし、個人的には、すべてのヘッダー ファイルを条件付きでコンパイルすることをお勧めします。ファイルはエラーを報告しませんが、コンパイル効率が低下し、コンパイラーが同じコードを複数回読み取って処理することになり、コンパイル時間とオーバーヘッドが増加するためです

ここに画像の説明を挿入

(9) しかし、ヘッダー ファイルが条件付きコンパイルで書かれていない場合、エラーが報告されるのはなぜでしょうか、と尋ねたい人もいます。構造定義をヘッダー ファイルに追加すると、すぐにエラーが表示されます。

ここに画像の説明を挿入

/**************  mian.c  **************/   
#include "a.h"
#include "b.h"
int main() 
{
    
    
    int result = add(three, 4);
    return 0;
}
/**************  a.h  **************/ 
#define three 3
int add(int a, int b);
extern int x;
struct student{
    
    
	char* name;
	int age;
	char* sex;
};
/**************  b.h  **************/ 
#include "a.h"

まとめ

(1) ヘッダー ファイルは実際にはディレクトリであり、モジュールの機能を読み取るのに便利です。一般に、ヘッダーファイルにはマクロ定義関数宣言extern 外部変数宣言、および構造型定義が含まれます。
(2) ヘッダー ファイルの名前は、対応する C ファイルの名前と一致している必要がありますが、攻撃されることを恐れない限り、一致していなくてもかまいません。
(3)#include の本質は、後でインクルードされるファイルの内容をコピーすることです。
(4) ヘッダファイルにヘッダファイルインクルードマクロ定義関数宣言extern 外部変数宣言のみが含まれている場合は、条件付きコンパイルを行わなくても構文エラーは発生しません。ただし、コンパイル効率が低下するため、お勧めできません。

ヘッダー ファイルの繰り返しのインクルードを防ぐ 2 つの方法

前述したように、ヘッダー ファイルに条件付きコンパイルを追加することをお勧めします。ここでは条件付きコンパイルの書き方を2つ紹介します。

#ifndef #定義 #endif

(1) おそらくほとんどの人は、この種の条件付きコンパイルしか知りません。Cライブラリで規定されている条件付きコンパイルです。
(2) 条件付きコンパイルを作成する人は、通常、次の形式で記述します。しかし、bh ファイルの条件付きコンパイルが __b_H_ になる理由を考えたことはありますか? 他のものに変更できますか?
(3) 答えは間違いなく「はい」です。これは実際にはプログラマのデフォルトのルールです。コンパイル条件を__nb_H_に変更することもできますが、これも条件付きコンパイルですが、皮がザラザラしていて分厚く、叩いた時の音が静かです。

/**************  标准写法  **************/ 
/**************  b.h  **************/ 
#ifndef   __b_H_
#define   __b_H_
//头文件的内容
#endif

/**************  不怕打写法  **************/ 
/**************  b.h  **************/ 
#ifndef   __nb_H_
#define   __nb_H_
//头文件的内容
#endif

#プラグマワンス

(1) これは C 言語で規定されている記述方法ではないすべてのコンパイラでサポートされることが保証されていないため、使用するとエラーが報告される可能性があります。
(2) この書き方は非常に簡単で、ヘッダファイルの一行目に #pragma を一度書くだけで、コンパイラが自動的に認識し、現在のヘッダファイルがコンパイルされるのは一度だけです。

/**************  b.h  **************/ 
#pragma once
//头文件的内容

さらに学習 #include

(1) 前述したように、#include は実際には前処理の段階で以下のファイルの内容を現在のファイルにコピーしています。では、#include の背後にあるのは .h ファイルのみでしょうか?
(2) もちろんそうではありません。#include の後に必要なファイルは何でも問題ありません。

ここに画像の説明を挿入

高度な学習ヘッダー ファイル

.h ファイルに C プログラムを記述させる

(1) ヘッダー ファイルを深く理解している場合、または先頭のコンテンツがほんの少しだけであれば、この記事ですべてが述べられています。間違いなくヘッドラインパーティーです。さて、乾物をアップロードし始めますが、本当にヘッダー ファイルには上記の 5 つの内容だけを記述することができますか?
(2) 私はこの質問をしましたが、答えは間違いなくノーです。#include が実際に次のファイルの内容を現在のファイルとしてコピーすることは上記でわかっていますが、プログラムはこのように記述できますか?

/**************  test_h.h  **************/ 
#include <stdio.h>

int main()
{
    
    
	printf("hello\r\n");
}	
/**************  test_h.c  **************/ 
#include "test_h.h"

(3) コンパイルは実行可能であることを示していますが、なぜですか? 同じ文ですが、C ファイルから実行可能ファイルまでの 4 つのステップのうち、コンパイル段階のみが構文検出を実行します。次に、#include "test_h.h" によって、プリコンパイル段階で test_h.h のコードが test_h.c にコピーされ、コンパイル段階で test_h.c に C プログラムがあることがわかりました。問題がないことは間違いありません。

ここに画像の説明を挿入

c ファイルはなく、コンパイル用のすべての .h ファイル

(1) ここですでに勉強したので、もっと大胆にみましょう。このプロジェクトに c ファイルを含めず、.h ファイルのみをコンパイルするようにして、どのような影響があるかを見てみましょう。

/**************  test_h.h  **************/ 
#include <stdio.h>

int main()
{
    
    
	printf("hello\r\n");
}	

(2) プロジェクト内に c ファイルはありませんが、ヘッダファイルだけをコンパイルしても問題ないことがわかります。file コマンドを使用してファイルを表示すると、それが GCC によってプリコンパイルされた C ヘッダー ファイル (バージョン 014) であることが示されます。

ここに画像の説明を挿入

(3) この時点で、もう終わってもいいのではないかと思う人もいるかもしれない。しかし、これは十分に深いものなのでしょうか?ここで終わってしまっては明らかに意味がありません。
(4) この質問は Ken 兄弟のコミュニケーション グループでのみ提起されます。Ken 兄弟はコンパイルに gcc を使用していることがわかりました。gcc -o テスト_h -xc テスト_h.hこの書き方。-xc が何なのかわからないので、gcc --help を使用して調べてください。
(6) -x: 以下の入力ファイルの言語を指定するという説明が表示されます。許可される言語は次のとおりです: c、c++、アセンブリ、なし 「なし」は、ファイルの拡張子に基づいて言語を推測するデフォルトの動作に戻ります
(7) 私が太字にした部分に注目してください。コンパイル先の言語の種類を指定しない場合は、ファイル拡張子に基づいて推測できると彼は言いました。ということで、どのファイルにコンパイルするかを指定してみましょう。

ここに画像の説明を挿入

(8) gcc でコンパイルしたプロジェクトを指定すると、ファイルが実行できることがわかります。したがって、.h ファイルには上記で指定した 5 つの内容を書き込むことができるだけでなく、C プログラムを含むあらゆるものを書き込むことができると結論付けることができます。
(9) このことから、c プロジェクトには .h ファイルのみを含めることができ、これは影響しないと結論付けることができます。

ここに画像の説明を挿入

任意のサフィックスファイルを付けて C プロジェクトを作成します

(1) ここで、内容が変更されない限り、ファイル名は自由に変更できることがわかります。gcc を指定してファイルを c ファイルとしてコンパイルする限り。すべてをコンパイルして渡すことができ、最終ファイルは正常に実行できます。
(2) この場合、.py ファイルを使って Windows 上で c プロジェクト ファイルを記述し、それをコンパイルするのではないかと言いたくなる人もいるかもしれません。
(3) ここで、IDE を使用してコンパイルすると、高い確率でエラーが報告されることが明記されています。IDE を開いてコンパイルするため、IDE はファイルの拡張子に基づいて言語を判断し、コンパイルします。

ここに画像の説明を挿入

要約する

実際、これらのファイル接尾辞は識別子であり、これがどのタイプのファイルであるかを示すために使用されます。ただし、Linux を使用している場合は、このサフィックスに好きな名前を付けることができます。とにかく、操作の余地があるので、コンパイラを復帰させます。(Windowsでこれができるかどうかは不明です)

おすすめ

転載: blog.csdn.net/qq_63922192/article/details/131797245