プロジェクトの規模が大きくなるにつれて、C コードをモジュール化するために知っている方法、慣例、規則は何ですか?
8 に答える
モジュールを使用するために必要なものだけを含むヘッダー ファイルを作成します。対応する .c ファイルで、外部に表示することを意図していないもの (ヘルパー関数など) を静的にします。名前空間の競合を避けるために、外部から見えるすべての名前にプレフィックスを使用します。(モジュールが複数のファイルにまたがっている場合、内部のものを公開する必要があり、「静的」でそれらを非表示にすることができないため、事態はより困難になります)
(私が C を改善しようとするなら、関数のデフォルトのスコープを「静的」にすることです。外部で何かを表示したい場合は、「エクスポート」または「グローバル」などでマークする必要があります。似ている。)
OO 手法は C コードに適用できますが、より規律が必要なだけです。
- オブジェクトを操作するには、不透明なハンドルを使用します。これがどのように行われるかの 1 つの良い例は、ライブラリです。すべてが不透明なハンドル
stdio
を中心に編成されています。FILE*
多くの成功したライブラリは、この原則に基づいて編成されています (例: zlib、apr ) 。 - s のすべてのメンバー
struct
は暗黙的に C に記述されているため、情報隠蔽public
の有用な手法を適用するには、慣例とプログラマーの規律が必要です。「プライベート メンバーは '_' で終わる」など、単純で自動的にチェック可能な規則を選びます。 - インターフェイスは、関数へのポインターの配列を使用して実装できます。確かにこれには、言語内サポートを提供する C++ のような言語よりも多くの作業が必要ですが、それでも C で実行できます。
High and Low-Level Cの記事には、多くの優れたヒントが含まれています。特に、「クラスとオブジェクト」セクションを見てください。
ANSI C でのコーディングの標準とスタイルには、適切なアドバイスが含まれています。
- ヘッダー ファイルで変数を定義しないでください。代わりに、ソース ファイルで変数を定義し、extern ステートメント (宣言) をヘッダーに追加します。これは#2と#3につながります。
- すべてのヘッダーでインクルード ガードを使用します。これにより、多くの頭痛が解消されます。
- #1 と #2 を実行したと仮定して、特定のファイルに必要なものすべて (必要なものだけ) をそのファイルに含めます。コンパイラがインクルード ディレクティブを展開する順序に依存しないでください。
関数は1つのことを実行し、この1つのことをうまく実行する必要があります。
大きなラッパー関数で使用される小さな関数の多くは、小さくて理解しやすい(そしてテストする!)ビルディングブロックからコードを構造化するのに役立ちます。
それぞれ2つの機能を備えた小さなモジュールを作成します。必要なものだけを公開し、モジュール内で他のものを静的に保ちます。小さなモジュールをそれらの.hインターフェースファイルとリンクします。
モジュール内の静的ファイルスコープ変数にアクセスするためのGetter関数とSetter関数を提供します。このように、変数は実際には1か所にのみ書き込まれます。これは、関数と呼び出しスタックのブレークポイントを使用して、これらの静的変数へのアクセスをトレースするのにも役立ちます。
モジュラーコードを設計する際の重要なルールの1つは、必要がない限り最適化を試みないことです。多くの小さな関数は通常、よりクリーンで構造化されたコードを生成し、追加の関数呼び出しオーバーヘッドはそれだけの価値があるかもしれません。
私は常に変数を関数内でも最も狭い範囲に保つようにしています。たとえば、forループのインデックスは通常、ブロックスコープに保持でき、関数レベル全体で公開する必要はありません。Cは、「使用する場所を定義する」というC ++ほど柔軟ではありませんが、機能します。
Pidgin (以前の Gaim) が使用するアプローチは、Plugin
構造体を作成することです。各プラグインは、構造体に、初期化と分解のためのコールバックと、その他の説明的な情報を設定します。構造体以外のほとんどすべてが静的として宣言されているため、Plugin 構造体のみがリンク用に公開されます。
次に、アプリの残りの部分と通信するプラグインの疎結合を処理するために (セットアップとティアダウンの間に何かを行うとよいので)、それらにはシグナリング システムがあります。プラグインは、アプリの任意の部分 (別のプラグインを含む) によって特定のシグナル (標準の C シグナルではなく、カスタムの拡張可能な [コードを設定するのではなく、文字列で識別される]) が発行されたときに呼び出されるコールバックを登録できます。また、シグナル自体を発行することもできます。
これは実際にはうまく機能しているようです - 異なるプラグインは互いに構築できますが、結合はかなり緩いです - 関数の直接呼び出しはなく、すべてがシグナリングシステムを介して行われます.
コードを関連する関数のライブラリに分割することは、物事を整理する 1 つの方法です。名前の競合を避けるために、接頭辞を使用して関数名を再利用できるようにすることもできますが、適切な名前を使用しても、これが大きな問題になることはありません。たとえば、独自の数学ルーチンを開発したいが、標準の数学ライブラリの一部を使用したい場合は、xyz_sin()、xyz_cos() などの文字列を前に付けることができます。
通常、私はファイルごとに 1 つの関数 (または密接に関連する関数のセット) と、ソース ファイルごとに 1 つのヘッダー ファイルの規則を好みます。各ディレクトリが個別のライブラリを表すディレクトリにファイルを分割することも良い考えです。一般に、さまざまなライブラリ/プログラムを表す階層に従って、システム全体のすべてまたは一部をビルドできるメイクファイルまたはビルド ファイルのシステムがあります。
ディレクトリとファイルはありますが、名前空間やカプセル化はありません。各モジュールを個別の obj ファイルにコンパイルし、それらを (ライブラリとして) 一緒にリンクできます。