C-#ifdefの代替
-
06-07-2019 - |
質問
私は、今日でも、それを維持するビルド担当者がソースファイルを取得し、次のセクションを手動で変更する前に、レガシー C コードの大部分を合理化しようとしています。さまざまなタイプの環境に基づいたコンパイル。
例は続きますが、ここに質問があります。 C にさびていますが、#ifdefの使用はお勧めできません。もっと良い選択肢を提供できますか?また、その一部(すべてではないにしても)を環境変数として設定するか、パラメーターとして渡すことができると思います。その場合、これらを定義してソースコードからアクセスする良い方法は何でしょうか。
ここで扱っているコードのスニペットです
#define DAN NO
#define UNIX NO
#define LINUX YES
#define WINDOWS_ES NO
#define WINDOWS_RB NO
/* Later in the code */
#if ((DAN==1) || (UNIX==YES))
#include <sys/param.h>
#endif
#if ((WINDOWS_ES==YES) || (WINDOWS_RB==YES) || (WINDOWS_TIES==YES))
#include <param.h>
#include <io.h>
#include <ctype.h>
#endif
/* And totally insane harcoded paths */
#if (DAN==YES)
char MasterSkipFile[MAXSTR] = "/home/dp120728/tools/testarea/test/MasterSkipFile";
#endif
#if (UNIX==YES)
char MasterSkipFile[MAXSTR] = "/home/tregrp/tre1/tretools/MasterSkipFile";
#endif
#if (LINUX==YES)
char MasterSkipFile[MAXSTR] = "/ptehome/tregrp/tre1/tretools/MasterSkipFile";
#endif
/* So on for every platform and combination */
解決
もちろん、コマンドラインで -DWHATEVER
を渡すことができます。または、 -DWHATEVER_ELSE = NO
など。たぶん、次のようなことができるパスについて
char MasterSkipFile[MAXSTR] = SOME_COMMAND_LINE_DEFINITION;
そしてパス
-DSOME_COMMAND_LINE_DEFINITION="/home/whatever/directory/filename"
コマンドラインで。
他のヒント
以前は、これらの定義を含む .h
ファイルを生成し、スクリプトで生成していました。これにより、多くの脆弱な#ifと#ifdefを取り除くことができました
そこに何を置くかについて注意する必要がありますが、マシン固有のパラメーターは良い候補です-これがautoconf / automakeの動作方法です。
EDIT:あなたの場合、例は、生成された .h
ファイルを使用して INCLUDE_SYS_PARAM
と INCLUDE_PARAM
を定義することです。コード自体の使用:
#ifdef INCLUDE_SYS_PARAM
#include <sys/param.h>
#endif
#ifdef INCLUDE_PARAM
#include <param.h>
#endif
新しいプラットフォームへの移植がはるかに容易になります-新しいプラットフォームの存在は、生成された .h
ファイルにのみコードを流し込みます。
プラットフォーム固有の構成ヘッダー
すべてのビルドで使用されるヘッダーにプラットフォーム固有の構成を生成するシステムが必要です。 AutoConf名は「config.h」です。 「platform.h」または「porting.h」または「port.h」またはテーマの他のバリエーションを見ることができます。このファイルには、構築中のプラットフォームに必要な情報が含まれています。バージョン管理されたプラットフォーム固有のバリアントを標準名にコピーすることにより、ファイルを生成できます。コピーする代わりにリンクを使用できます。または、構成スクリプトを実行して、スクリプトがマシンで検出した内容に基づいてその内容を決定できます。
構成パラメーターのデフォルト値
コード:
#if (DAN==YES)
char MasterSkipFile[MAXSTR] = "/home/dp120728/tools/testarea/MasterSkipFile";
#endif
#if (UNIX==YES)
char MasterSkipFile[MAXSTR] = "/home/tregrp/tre1/tretools/MasterSkipFile";
#endif
#if (LINUX==YES)
char MasterSkipFile[MAXSTR] = "/ptehome/tregrp/tre1/tretools/MasterSkipFile";
#endif
次のものに置き換えてください:
#ifndef MASTER_SKIP_FILE_PATH
#define MASTER_SKIP_FILE_PATH "/opt/tretools/MasterSkipFile"
#endif
const char MasterSkipFile[] = MASTER_SKIP_FILE_PATH;
ビルドを別の場所に配置する場合は、次の方法で場所を設定できます。
-DMASTER_SKIP_FILE_PATH='"/ptehome/tregtp/tre1/tretools/PinkElephant"'
一重引用符と二重引用符の使用に注意してください。パスにバックスラッシュを使用して、コマンドラインでこれを行わないようにしてください。あらゆる種類のものに同様のデフォルトメカニズムを使用できます。
#ifndef DEFAULTABLE_PARAMETER
#define DEFAULTABLE_PARAMETER default_value
#endif
デフォルトを適切に選択すると、多くのエネルギーを節約できます。
再配置可能ソフトウェア
1つの場所にしかインストールできないソフトウェアの設計についてはわかりません。私の本では、新しいバージョン2.1と同時に古いバージョン1.12の製品をマシンにインストールできるようにする必要があり、それらは独立して動作できるはずです。ハードコードされたパス名はそれを打ち負かします。
プラットフォームではなく機能別にパラメーター化
AutoConfツールと平均的な代替システムの主な違いは、プラットフォームではなく機能に基づいて設定が行われることです。コードをパラメーター化して、使用する機能を識別します。機能はオリジナル以外のプラットフォームで表示される傾向があるため、これは非常に重要です。次のような行があるコードを管理します。
#if defined(SUN4) || defined(SOLARIS_2) || defined(HP_UX) || \
defined(LINUX) || defined(PYRAMID) || defined(SEQUENT) || \
defined(SEQUENT40) || defined(NCR) ...
#include <sys/types.h>
#endif
持っている方がはるかに良いでしょう:
#ifdef INCLUDE_SYS_TYPES_H
#include <sys/types.h>
#endif
そして、それが必要なプラットフォームで、以下を生成します:
#define INCLUDE_SYS_TYPES_H
(このヘッダー例を文字通りに受け取らないでください。これは私が乗り越えようとしている概念です。)
機能のバンドルとしてプラットフォームを扱う
前のポイントの結果として、プラットフォームを検出し、そのプラットフォームに適用可能な機能を定義する必要があります。ここには、構成機能を定義するプラットフォーム固有の構成ヘッダーがあります。
ヘッダーで製品機能を有効にする必要があります
(別の回答に対して行ったコメントの詳細)
条件付きで含めるか除外する必要がある製品の機能がたくさんあるとします。例:
KVLOCKING
B1SECURITY
C2SECURITY
DYNAMICLOCKS
適切な定義が設定されると、関連するコードが含まれます。
#ifdef KVLOCKING
...KVLOCKING stuff...
#else
...non-KVLOCKING stuff...
#endif
cscope などのソースコード分析ツールを使用している場合は、表示できると便利です。 KVLOCKINGが定義されている場合。定義されている唯一の場所がビルドシステムの周りに散らばっているランダムなMakefileにある場合(これで使用されるサブディレクトリが100個あると仮定しましょう)、コードがまだ使用されているかどうかを判断するのは困難ですプラットフォーム。定義がどこかのヘッダー(プラットフォーム固有のヘッダー、または製品リリースヘッダー)にある場合(バージョン1.xにはKVLOCKINGを含めることができ、バージョン2.xにはC2SECURITYを含めることができますが、2.5にはB1SECURITYなどが含まれます)、 KVLOCKINGコードはまだ使用中です。
私を信じてください、20年の開発とスタッフの交代の後、人々は機能がまだ使用されているかどうかわかりません(安定していて問題を引き起こさないため-おそらく使用されないため)。また、KVLOCKINGがまだ定義されているかどうかを確認する唯一の場所がMakefiles内にある場合、cscopeなどのツールはあまり役に立たないため、後でクリーンアップしようとすると、コードの変更によりエラーが発生しやすくなります。
使用するのは非常に賢い:
#if SOMETHING
..プラットフォーム間で、壊れたプリプロセッサを混乱させないため。しかし、最新のコンパイラーは、最終的に事実上あなたの主張を議論するはずです。プラットフォーム、コンパイラ、およびプリプロセッサの詳細を入力すると、より簡潔な回答が得られる場合があります。
条件付きコンパイルは、多数のオペレーティングシステムとその中のバリアントを考えると、必要な悪です。 ifdefなどが最も明確にプリプロセッサの悪用ではない場合は、意図したとおりに実行します。
私の望ましい方法は、ビルドシステムにOSの検出を行わせることです。複雑なケースでは、マシン固有のものを単一のソースファイルに分離し、OSごとに完全に異なるソースファイルを作成します。
この場合、そのファイルには #include&quot; OS_Specific.h&quot;
が含まれます。別のインクルードと、このプラットフォームのMasterSkipFileの定義を配置します。コンパイラーのコマンドラインで異なる -I
(パスディレクトリを含む)を指定することにより、それらの間で選択できます。
この方法で行うことの良い点は、コードを把握しようとする人(おそらくデバッグ)が、実行されていないプラットフォームのファントムコードを歩き回る(おそらく誤解される)必要がないことです。 。
ほとんどのソースファイルが次のように起動するビルドシステムを見てきました。
#include PLATFORM_CONFIG
#include BUILD_CONFIG
そしてコンパイラは次のものでキックオフされました:
cc -DPLATFORM_CONFIG="linuxconfig.h" -DBUILD_CONFIG="importonlyconfig.h"
(バックスラッシュエスケープが必要な場合があります)
これにより、あるファイルセットのプラットフォーム設定と別のファイルセットの構成設定を区別できるようになりました。プラットフォーム設定は、1つのプラットフォーム上に存在しないか、適切な形式でないライブラリ呼び出しの処理を管理し、重要なサイズ依存型(プラットフォーム固有のもの)を定義します。ビルド設定は、出力で有効になっている機能を処理します。
一般性
私は、GNU Autotools教会から追放された異端者です。どうして?私のツールが何をしているのかを理解したいからです。また、2つのコンポーネントを組み合わせようとした経験があるため、それぞれのコンポーネントは、互換性のない異なるバージョンのautotoolsがコンピューターにインストールされているデフォルトバージョンであると主張していました。
私は、プラットフォームと重要な抽象化のすべての組み合わせに対して1つの.hファイルまたは.cファイルを作成して作業しています。 インターフェイスが何であるかを示す中央の.hファイルを定義するために努力しています。多くの場合、これは「互換性レイヤー」を作成することを意味します。それはプラットフォーム間の違いから私を隔離します。プラットフォーム固有の機能ではなく、可能な限りANSI標準Cを使用することがよくあります。
スクリプトを書いて、プラットフォーム依存のファイルを生成することがあります。しかし、スクリプトは常に手書きで文書化されているので、何をするのか知っています。
Glenn Fowlerの nmake
とPhong Voの iffe
(機能が存在する場合)を賞賛します。これらはGNUツールよりも優れた設計だと思います。しかし、これらのツールはAT&amp; T Software Technologyスイートの一部であり、ASTの方法全体を購入せずにそれらを使用する方法を見つけることができませんでした。 >
あなたの例
明らかに必要がある
extern char MasterSkipFile[];
どこかの.hファイルで、適切な.oに対してリンクできます
「プラットフォーム用の.hファイルの正しいセット」の条件付きインクルード可能な場合はANSI Cに固執し、不可能な場合はプラットフォーム固有の.hファイルで互換性レイヤーを定義しようとすることで処理できます。現状では、 #include
がインポートしようとしている名前がわからないため、より具体的なアドバイスをすることはできません。