ヘッダーファイルの大学は、C言語がこれらの原則に注意を払う必要があることを求めています...

上の「UncleWheat」をクリックして「Top/ StarPublicAccount」を選択してください

福祉乾物、お早めにお届け

序文

理解できないことがたくさんありますが、実際にプロジェクトで使ってみると問題があります。

私は自分のコーディングスタイルに自信を持っていたので、C言語の記述、特にソフトウェアファイルのエンジニアリング管理に非常に精通していると思っていました。(結局のところ、私が卒業したときに上司が私に与えた最初のトレーニングは、エンコード形式の正規化でした)

.cファイルは.hファイルに対応すると思いました。

cファイルには独自の.hファイルのみが含まれます。他のファイルのコンテンツが.cファイルで使用されている場合、.hファイルには使用済みヘッダーファイルを含めることができます。

私はこの概念を念頭に置いてコードを書いているようです(とてもひどいです)。プロジェクトファイルの数が少ない場合、この概念は問題にならないようです。

しかし、プロジェクトファイルの数が増えると、このアイデアには欠点があることがわかります。头文件互相包含,导致编译时自以为有些宏变量声明了,它就能起作用,但实际测试发现这种方式编码后,有些声明的宏没能起到作用。

リーダーや同僚によって修正された後、私は元のコードの書き方が間違っていることに気づきました。.cファイルに対応する.hファイルには、ヘッダーファイルで使用されている他のファイルのヘッダーファイルのみが含まれている必要があり、必須ではない.hファイルは含まれていない必要があります。

.cファイルには、使用されるすべての.hファイルが含まれています。.cファイルのヘッダーファイルが繰り返しインクルードされている場合でも、このように記述しても問題はありません。

言語の説明が抽象的すぎる場合があり、記号が例として使用されます。AcとBcの2つの.cファイルがある場合、当然、それらには独自のAhファイルとBhファイルがあります。

元のアイデア:

Acには#include"Ah"が1つしかなく、AhにはBh、Ch、Dhなどのファイルがたくさん含まれています。AcファイルではBh、Ch、Dhの内容が使用されているためです。図1に示すように。

新しいアイデア:

Ahには、Ahが書き込む.hファイルのみが含まれます。多くの場合、Ahに.hファイルは必要ありません。Acファイルでは、#include "Bh" #include "Ch"#include"と記述する必要があります。 Dh」。また、2つのファイルの.cファイルは、ヘッダーファイルのインクルードに相互にインクルードできます。図2に示すように。

aa9013b989c09fd0b8d3d74447452a93.png

03cdf8f657225e9d60ec0fceaa663247.png

プロジェクトで発生したヘッダーファイルの包含の問題により、問題を深く理解するために情報を再検索することになりました。したがって、以下は、ネットワークリソースの検索と、関連するコンテンツを整理するための私自身の理解です。私はそれに興味があることを望みます。仲間は助けました。

バックグラウンド

C言語の場合、ヘッダーファイルのデザインはシステムデザインのほとんどを反映しています。不合理なヘッダーファイルのレイアウトはコンパイル時間が長くなる根本的な原因であり、不合理なヘッダーファイルは実際には不合理な設計です。

頼る

具体的には、コンパイルの依存関係を指します。xhにyhが含まれている場合、xはyに依存すると言われます。xhにはyhが含まれ、yhにはzhが含まれるなどの依存関係が実行され、xはzからyに依存します。依存関係があると、コンパイル時間が長くなります。

依存関係は避けられず必要ですが、設計が不十分だとシステム全体の依存関係が非常に複雑になるため、1つのファイルを変更すると、システム全体を再コンパイルする必要があり、コンパイル時間が大幅に増加します。

適切に設計されたシステムでは、1つのファイルを変更するには、複数のファイル、または1つのファイルを再コンパイルするだけで済みます。

ある製品は、ツールを介してすべての関数の実装をコメントアウトする実験を行ったことがあり、コンパイル時間は10%未満しか短縮されませんでした。その理由は、AにBが含まれ、BにCが含まれ、CにDが含まれるためです。結局、ほとんどすべてのソースファイルにはプロジェクトグループのすべてのヘッダーファイルが含まれているため、コンパイル時間のほとんどはヘッダーファイルの解析に費やされます。

特定の製品には、ツールを使用して.cファイルをより大きな.cファイルにマージするための「ベストプラクティス」もあり、それによってコンパイル効率が大幅に向上します。

基本的な理由は、.cファイルをマージすることでヘッダーファイルの解析回数が減ることです。ただし、このような「グッドプラクティス」は、.cファイルの適切な分割の中断です。

ほとんどの製品は、1つのコードを変更するためにプロジェクト全体をコンパイルする必要があります。TDDなどのプラクティスでは、モジュールレベルでのコンパイル時間を秒単位で制御する必要があります。分散コンパイルを使用しても、達成するのは難しく、合理的です。ヘッダーは最終的に分割する必要があります。ファイルとヘッダーファイルのインクルード関係により、コンパイル時間が大幅に短縮されます。

「GoogleC++スタイルガイド」1.2ヘッダーファイルの依存関係の章にも同様の説明があります。ヘッダーファイルaa.hが含まれている場合、新しい依存関係が導入されます。aa.hが変更されると、aa。hコードが直接的および間接的に含まれます。再コンパイルされます。

aa.hにbb.hなどの他のヘッダーファイルが含まれている場合、bb.hに変更を加えると、aa.hを含むすべてのコードが再コンパイルされます。

アジャイル開発手法では、コードが頻繁にビルドされ、コンパイル時間が長くなると、頻繁なビルドが大幅に妨げられます。したがって、コードを変更した後のコンパイル時間を制御するために、特にヘッダーファイルにヘッダーファイルを含めることを減らす傾向があります。

ヘッダーファイルの合理的な分割は、システム設計のアイデアを反映していますが、プログラミング仕様の観点から、ヘッダーファイルを合理的に計画するためのいくつかの一般的な方法がまだあります。この章で紹介する方法のいくつかは、ヘッダーファイルの合理的な計画に役立ちます。

原則1:インターフェイスの宣言はヘッダーファイルに配置する必要があり、実装は配置しないでください。

説明:ヘッダーファイルは、 模块( Module)OR单元( Unit)の外部インターフェイスです。外部で提供される関数宣言、マクロ定義、型定義などの外部宣言は、ヘッダーファイルに配置する必要があります。

  • 内部で使用される関数(クラスのプライベートメソッドと同等)宣言は、ヘッダーファイルに配置しないでください。

  • 内部で使用されるマクロ、列挙型、構造体の定義は、ヘッダーファイルに入れないでください。

  • 変数定義はヘッダーファイルに配置しないでください。.cファイルに配置する必要があります。

  • ヘッダーファイルに変数宣言を入れないようにしてください。つまり、グローバル変数をインターフェイスとして使用しないようにしてください。変数は、モジュールまたはユニットの内部実装の詳細であり、ヘッダーファイルで宣言することによって外部に直接公開することはできませんが、機能インターフェイスによって公開する必要があります。

さらに読む資料:「C言語のインターフェースと実装」

原則2:ヘッダーファイルには単一の責任が必要です。

説明:ヘッダーファイルが複雑すぎ、依存関係が複雑すぎます。これが、コンパイル時間が長い主な理由です。多くの既存のコードでは、ヘッダーファイルが大きすぎて、責任が多すぎて、循環依存の問題が相まって、.cでマクロを使用するために12を超えるヘッダーファイルが含まれる可能性があります。

ヘッダーファイルは、基本的なデータ型WORDを定義するだけでなく、stdio.hsyslib.hやその他のあまり一般的に使用されないヘッダーファイルも含みます。

プロジェクトに10,000個のソースファイルがあり、そのうち100個がstdio.hのprintfを使用している場合、上記のヘッダーファイルの責任が大きすぎ、各ファイルにWORDをインクルードする必要があるため、stdio.h/syslibになります。 .hなどが不必要に9900倍に拡張され、プロジェクトのコンパイル時間が大幅に増加する可能性があります。

原則3:ヘッダーファイルは安定性の方向にインクルードする必要があります。

注:ヘッダーファイルのインクルード関係は一種の依存関係です。一般的に、不安定なモジュールは安定したモジュールに依存するようにする必要があります。これにより、不安定なモジュールが変更されても、安定したモジュールが影響を受けない(コンパイルされない)ようになります。

当社の製品に関する限り、依存関係の方向は次のようになります。製品はプラットフォームに依存し、プラットフォームは標準ライブラリに依存します。製品ラインプラットフォームのコードにはすでに製品ヘッダーファイルが含まれているため、プラットフォームを個別にコンパイル、リリース、およびテストできません。これは非常に悪い反例です。

安定したモジュールに依存する不安定なモジュールの代わりに、両方のモジュールが一緒にインターフェースに依存するためのより良いアプローチは、どちらかのモジュールの内部実装変更が他方を再コンパイルする必要がないようにすることです。ここでは、インターフェース自体が最も安定していると仮定します。

参考資料:編集者は、開発者が「依存性逆転」の原則を使用することを推奨しています。つまり、ユーザーがインターフェースを作成し、サービスプロバイダーがインターフェースを実装します。より具体的な説明については、「アジャイルソフトウェア開発:原則」を参照してください。 「パターンと実践」(Robert C. Martin著、Deng Hui著(清華大学出版社、2003年9月翻訳)、「アジャイルデザイン」の章の第2部。

ルール1:各.cファイルには、同じ名前の.hファイルが必要です。このファイルは、外部に公開する必要のあるインターフェイスを宣言するために使用されます。

注:.cファイルがインターフェースを公開する必要がない場合は、メイン関数が配置されているファイルなど、プログラムのエントリポイントでない限り存在しないようにする必要があります。

一部の既存の製品では、.cファイルが2つのヘッダーファイルに対応するのが通例です。1つは外部に公開されたインターフェイスを格納するためのもので、もう1つは行数を制御するために内部で使用する必要のある定義と宣言を格納するためのものです。 .cファイル内のコードの。編集者はこのスタイルを支持していません。

このスタイルの根本は、ソースファイルが大きすぎることです。最初に考慮すべきことは、.cファイルを分割して大きすぎないようにすることです。さらに、プライベート定義と宣言が別のヘッダーファイルに配置されると、他の人がそれらを含めることを技術的に防ぐことは不可能であり、これらの定義が実際にプライベートであると保証することは困難です。

この規則の逆は必ずしも当てはまりません。コマンドID定義ヘッダーファイルなどの一部の非常に単純なヘッダーファイルは、対応する.cが存在する必要はありません[a1]。

例:次のシナリオで、.cに関数呼び出し関係がある場合:

void foo()
{
 bar();
}

void bar()
{
 Do something;
}

barはfooの前に宣言する必要があります。そうしないと、コンパイルエラーが発生します。

このタイプの関数宣言は、次のように.cの先頭で宣言し、静的として宣言する必要があります。

static void bar();

void foo()
{
 bar();
}

void bar()
{
 Do something;
}

ルール2:ヘッダーファイルの循環依存を禁止します。

注:ヘッダーファイルは循環的に依存しています。つまり、ahにはbhが含まれ、bhにはchが含まれ、chにはahが含まれます。ヘッダーファイルを変更すると、ah / bh/chを含むすべてのコードが再コンパイルされます。

ahにbhが含まれ、bhにchが含まれ、chにヘッダーファイルが含まれないなど、一方向の依存関係である場合、ahを変更しても、bh/chを含むソースコードは再コンパイルされません。

ルール3:.c/.hファイルに未使用のヘッダーファイルを含めることは禁止されています。

注:多くのシステムのヘッダーファイルは、含めるのが複雑です。トラブルを回避するために、開発者はそれらを1つずつ調べず、考えているすべてのヘッダーファイルを直接インクルードする場合があります。一部の製品はgod.hをリリースします。これにはすべてのヘッダーファイルが含まれています。その後、さまざまなプロジェクトグループにリリースされて使用されます。このような方法でしばらくの間だけトラブルを回避すると、システム全体のコンパイル時間がさらに悪化し、メンテナンスに大きな問題が発生します。後の人々の。

ルール4:ヘッダーファイルは自己完結型である必要があります

説明:簡単に言えば、自己完結型とは、任意のヘッダーファイルを個別にコンパイルできることを意味します。ヘッダーファイルにヘッダーファイルが含まれているが、機能する別のヘッダーファイルも含まれている場合、通信バリアが増加し、ヘッダーファイルのユーザーに不要な負担がかかります[a2]。

例:ahが自己完結型でない場合、コンパイルするためにbhをインクルードする必要があります。これは害をもたらします。ahヘッダーファイルを使用するすべての.cファイルには、インポートされたahコンテンツをコンパイルするために追加のヘッダーファイルbhを含める必要があります。追加のヘッダーファイルbhは、ahの前にインクルードする必要があります。これにより、インクルードの順序に依存します。

注:このルールは、「。c / .hファイルは未使用のヘッダーファイルのインクルードを禁止します」ルールと一緒に使用する必要があります。ahを自己完結型にするために、ahに不要なヘッダーファイルをインクルードすることはできません。ahは今すぐ自己完結型である必要があり、自己完結型以外の他のヘッダーファイルをahにインクルードすることはできません。

ルール5:常に内部の#includeガード(#defineガード)を記述します。

注:ヘッダーファイルを複数回インクルードすることは、慎重に設計することで回避できます。そうしないと、ヘッダーコンテンツが複数回含まれるのを防ぐメカニズムが必要になります。

通常のアプローチは、ファイルごとにマクロを構成し、ヘッダーファイルが最初にインクルードされたときにこのマクロを定義し、ヘッダーファイルが再びインクルードされたときにファイルの内容を除外するために使用します。

すべてのヘッダーファイルは、ヘッダーファイルが複数含まれないように#defineを使用する必要があります。名前の形式はFILENAME_Hです。一意性を確保するために、より適切な名前はPROJECTNAME_PATH_FILENAME_Hです。

注:マクロの先頭に「」はありません。つまり、 「」と「」で始まる識別子は通常、システムによって予約されているか、標準ライブラリによって使用されるため、_FILENAME_Hの代わりにFILENAME_Hが使用されます。グローバル可視性の場合の検査ツール「」で始まる識別子は警告を発します。

定義にプロテクターが含まれている場合は、次のルールに従う必要があります。

1)プロテクターは一意の名前を使用します。

2)保護されたセクションの前後にコードやコメントを配置しないでください。

例:ディレクトリがVOS / include / timer / timer.hであるVOSプロジェクトのタイマーモジュールのtimer.hは、次のように保護する必要があると想定します。

#ifndef VOS_INCLUDE_TIMER_TIMER_H

#define VOS_INCLUDE_TIMER_TIMER_H

...

#endif

次の簡単な方法を使用して保護することもできます。

#ifndef TIMER_H

#define TIMER_H

..

#endif

例外:ヘッダーファイルの著作権ステートメント部分とヘッダーファイルの全体的なコメント部分(このヘッダーファイルの開発背景の説明、使用上の注意など)は、保護文字(#ifndef)の前に配置できます。 XX_H)。

ルール6:ヘッダーファイルで変数を定義することは禁止されています。

説明:ヘッダーファイルに変数を定義すると、ヘッダーファイルが他の.cファイルに含まれているため、変数が繰り返し定義されます。

規則7:他の.cによって提供されるインターフェースは、ヘッダーファイルをインクルードすることによってのみ使用できます。.cのexternによって外部関数インターフェースと変数[a3]を使用することは禁止されています。

説明:acがbcで定義されたfoo()関数を使用する場合、bhでextern int foo(int input)を宣言し、acで#includeを介してfooを使用する必要があります。

acでexternintfoo(int input);を直接記述してfooを使用することは禁止されています。これにより、fooが変更されたときに宣言と定義に一貫性がなくなる可能性があります[a4]。

規則8:extern"C"にヘッダーファイルを含めることは禁止されています。

説明:extern "C"にヘッダーファイルを含めると、extern"C"がネストされます。VisualStudioにはextern"C"のネストレベルに制限があります。ネストレベルが多すぎると、コンパイルエラーが発生します。

extern "C"にヘッダーファイルをインクルードすると、インクルードされたヘッダーファイルの本来の意図が損なわれる可能性があります。たとえば、2つのヘッダーファイルahとbhがあります。

C++プリプロセッサでbhを拡張すると

extern "C"
{
  void foo(int);
  void b();
}

ahの作者の当初の意図によれば、関数fooはC ++フリー関数であり、そのリンク仕様は「C++」です。しかし、bhでは、#include"ah"がextern"C" {}内に配置されたため、関数fooのリンケージ仕様が誤って変更されました。

例:誤った使用法:

extern "C"
{
  #include "xxx.h"
  ...
}

正しい使用法:

#include "xxx.h"

extern "C"
{
  ...
}

推奨事項1:モジュールには通常複数の.cファイルが含まれているため、それらを同じディレクトリに配置することをお勧めします。ディレクトリ名はモジュール名です。外部ユーザーの便宜のために、各モジュールでディレクトリ名のファイル名を.hに指定することをお勧めします。

注:この.hには、すべての内部.hsが含まれているだけではないことに注意してください。これは、モジュールユーザーの便宜のために全体として提供されるモジュールインターフェイスです。

Googleテスト(略してGTest)を例にとると、GTestは全体としてC ++ユニットテストフレームワークを提供します。gtestプロジェクトの1.5バージョンには、6つのソースファイルと12のヘッダーファイルがあります。

ただし、外界に提供するgtest.hは1つだけです。gtest.hが含まれている限り、GTestが提供する外部提供機能をすべて使用できます。ユーザーはGTest内のさまざまなファイル間の関係を関連付ける必要はありません。 、ソースファイルcを2つのソースファイルに分割するなど、将来GTestの内部実装が変更された場合でも、外部関数が変更されていなくても、ユーザーは気にする必要はありません。再コンパイルされます。

一部のモジュールでは、それらの内部機能は比較的緩く、必ずしもこの.hを提供する必要はないかもしれませんが、各サブモジュールまたは.cのヘッダーファイルを直接提供します。

たとえば、製品で一般的に使用されるVOSは、大きなモジュールとして内部に多くのサブモジュールがあり、それらの間の関係は比較的緩いため、vos.hを提供することは適切ではありません。また、メモリなどのVOSのサブモジュール(たとえば、実際の状況とは異なる場合があります)では、内部実装に複数の.cと.hが含まれている場合でも、内部実装は非常にまとまりがありますが、必要なのは1つだけです。外部から提供されます。Memory.hはインターフェースを宣言します。

推奨事項2:モジュールに複数のサブモジュールが含まれている場合は、各サブモジュールで外部.hにサブモジュールのファイル名を指定することをお勧めします。

説明:インターフェースユーザーの作成の難しさを軽減します。

推奨事項3:ヘッダーファイルに.incなどの非慣用的な拡張子を使用しないでください。

注:現在、多くの製品はヘッダーファイル拡張子として.incを使用していますが、これはC言語のイディオムに準拠していません。ヘッダーファイル拡張子として.incを使用する製品では、このヘッダーファイルをプライベートヘッダーファイルとして識別するのが通例です。

しかし、製品の実際のコードからは、これはフォローされておらず、1つの.incファイルがどこにでも複数の.cによって含まれています。この仕様では、ヘッダーファイルで個別のプライベート定義を推奨していません。詳細については、ルール1.1を参照してください。

さらに、.incを使用すると、ソースインサイトやVisual stduioなどのIDEツールがそれをヘッダーファイルとして認識せず、「変数定義へのジャンプ」などの多くの機能が使用できなくなります。

IDEに設定によってヘッダーファイルとして.incを強制的に認識させることは可能ですが、一部のソフトウェアは設定できません。たとえば、Visual Assistは.hのみを認識でき、設定によって.incを認識できません。

推奨事項4:同じ製品にヘッダーファイルの配置が一律に含まれています。

説明:インクルードヘッダーファイルの一般的な配置:関数ブロックの並べ替え、ファイル名の昇順、安定性の並べ替え。

安定性を確保するために、不安定なヘッダーファイルを前面に配置することをお勧めします。たとえば、次のように、製品ヘッダーファイルをプラットフォームヘッダーファイルの前面に配置します。

相対的に言えば、product.hはより頻繁に変更されます。エラーがある場合は、platform.hをコンパイルせずにproduct.hでエラーを見つけることができます。これにより、コンパイル時間が部分的に短縮されます。


[a1]たとえば、一部の画面ドライバーアドレスファイル、一部のプロトコル形式定義ファイル。.cまたは.hのみが存在でき、必ずしも両方が存在する必要はありません。

[a2]自己完結についてはよくわかりませんが、.hファイルに不要なヘッダーファイルをできるだけ含めないようにする必要があることを理解しています。場合によっては、他のヘッダーファイルを含める必要があります。

[a3]この方法は私のコードでよく使用されますが、後でできるだけ避ける必要があり、関数はヘッダーファイルを呼び出すことで使用されます。

[a4]はい、私はそれに遭遇しました。エンジニアリングの量が増えるにつれ、foo関数は次のいずれかの詳細で調整されましたが、externがそれを呼び出した他の場所は時間内に修正されず、KEILコンパイラはエラーを報告しなかったため、出現につながりましたバグの数、そして見つけるのは簡単ではありませんでした。

著者:ミルクカバー紅茶

元のリンクhttps ://blog.csdn.net/fengcq126/article/details/103016917

著作権は原作者に帰属します。侵害がある場合は、連絡して削除してください。

- 終わり -

過去に推奨

実際の戦闘で要約された7つのCプログラム、良いものは隠されていません

組み込みマスターになるには、これらの100を超えるソフトウェアおよびハードウェアのオープンソースプロジェクトが不可欠です。

わかりやすく説明しますが、組み込みOTAテクノロジーはどれほど複雑ですか?

申し訳ありません!サークル内のベテランによって要約された18の埋め込まれたCの実践的な経験

組み込み技術交換グループに参加し、一緒に進歩する

名刺をクリックしてフォローしてください

3f6921b725f6c7d6af5f16271942c421.png

あなたが注文したものはすべて良さそうです、私はそれが好きなのでそれを真剣に受け止めます

おすすめ

転載: blog.csdn.net/u010632165/article/details/123081380