4

私はレガシー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 */
4

7 に答える 7

13

-DWHATEVERもちろん、コマンドラインを渡すことができます。または-DWHATEVER_ELSE=NO、など。たぶんあなたが次のようなことをすることができるパスのために

char MasterSkipFile[MAXSTR] = SOME_COMMAND_LINE_DEFINITION;

そして合格

-DSOME_COMMAND_LINE_DEFINITION="/home/whatever/directory/filename"

コマンドラインで。

于 2009-10-27T17:36:02.357 に答える
5

以前は、.hこれらの定義を含むファイルを生成し、それをスクリプトで生成していました。これにより、多くの脆弱な #if と #ifdef を取り除くことができました

そこに何を入れるかについて注意する必要がありますが、マシン固有のパラメータは良い候補です - これが autoconf/automake の仕組みです。

編集:あなたの場合、例は、生成された.hファイルを使用して and を定義INCLUDE_SYS_PARAMINCLUDE_PARAM、コード自体で次を使用することです:

#ifdef INCLUDE_SYS_PARAM
#include <sys/param.h>
#endif

#ifdef INCLUDE_PARAM
#include <param.h>
#endif

新しいプラットフォームへの移植がはるかに簡単になります。新しいプラットフォームの存在は、生成された.hファイルにのみ、コードには含まれません。

于 2009-10-27T17:49:40.003 に答える
4

プラットフォーム固有の構成ヘッダー

すべてのビルドで使用されるヘッダーにプラットフォーム固有の構成を生成するシステムが必要です。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 か所にしかインストールできないソフトウェアの設計についてはよくわかりません。私の本では、製品の古いバージョン 1.12 を新しい 2.1 バージョンと同時にマシンにインストールできる必要があり、それらは独立して動作できる必要があります。ハードコーディングされたパス名はそれを無効にします。

プラットフォームではなく機能でパラメータ化

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 がまだ定義されているかどうかを確認する唯一の場所が Makefile 内にある場合、cscope などのツールはあまり役に立ちません。後でクリーンアップしようとすると、コードを変更するとエラーが発生しやすくなります。

于 2009-10-27T18:50:04.827 に答える
3

使用するのは非常に賢いです:

#if SOMETHING

..壊れたプリプロセッサを混乱させないように、プラットフォームからプラットフォームへ。ただし、最新のコンパイラは、最終的には効果的にあなたの主張を主張する必要があります。プラットフォーム、コンパイラ、およびプリプロセッサの詳細を提供すると、より簡潔な回答が得られる可能性があります。

多数のオペレーティングシステムとそのバリアントを考えると、条件付きコンパイルは必要悪です。if、ifdefなどがプリプロセッサの乱用ではないことは明らかであり、意図したとおりに実行するだけです。

于 2009-10-27T17:45:23.163 に答える
3

私の好みの方法は、ビルド システムに OS の検出を行わせることです。複雑なケースでは、マシン固有のものを 1 つのソース ファイルに分離し、OS ごとに完全に異なるソース ファイルを用意します。

したがって、この場合、#include "OS_Specific.h"そのファイルに a があります。さまざまなインクルードと、このプラットフォーム用の MasterSkipFile の定義を配置します。-Iコンパイラ コマンド ラインで別の (インクルード パス ディレクトリ) を指定することで、それらの中から選択できます。

このようにすることの良い点は、コードを理解しようとしている人 (おそらくデバッグ) が、実行していないプラットフォームのファントム コードを通り抜ける (そしておそらく誤解される) 必要がないことです。

于 2009-10-27T17:56:51.830 に答える
2

ほとんどのソース ファイルが次のようなものから始まるビルド システムを見てきました。

#include PLATFORM_CONFIG
#include BUILD_CONFIG

コンパイラは次のように開始されました。

cc -DPLATFORM_CONFIG="linuxconfig.h" -DBUILD_CONFIG="importonlyconfig.h"

(これにはバックスラッシュのエスケープが必要な場合があります)

これにより、プラットフォーム設定を 1 つのファイル セットに分離し、構成設定を別のファイル セットに分離できるようになりました。プラットフォーム設定は、1 つのプラットフォームに存在しない、または適切な形式ではない可能性があるライブラリ呼び出しの処理を管理し、サイズに依存する重要な型 (プラットフォーム固有のもの) を定義します。ビルド設定は、出力で有効になっている機能を処理します。

于 2009-10-27T18:08:33.913 に答える
1

一般性

私は GNU Autotools 教会から追放された異端者です。なんで?自分のツールが何をしているのかを理解したいからです。そして、私は 2 つのコンポーネントを組み合わせようとした経験があり、それぞれが異なる互換性のないバージョンの autotools が私のコンピューターにインストールされているデフォルトのバージョンであると主張していました。

私は、プラットフォームと重要な抽象化のすべての組み合わせに対して 1 つの .h ファイルまたは .c ファイルを作成することによって作業します。私は、インターフェースが何であるかを示す中央の .h ファイルを定義するために懸命に取り組んでいます。多くの場合、これは、プラットフォーム間の違いから隔離する「互換性レイヤー」を作成することを意味します。多くの場合、プラットフォーム固有の機能ではなく、可能な限り ANSI 標準 C を使用することになります。

プラットフォームに依存するファイルを生成するスクリプトを書くことがあります。しかし、スクリプトは常に手書きで文書化されているので、スクリプトが何をするかはわかっています。

私は、Glenn Fowlernmakeと Phong Vo のiffe(機能が存在する場合) を高く評価しています。これらは、GNU ツールよりも優れた設計になっていると思います。しかし、これらのツールは AT&T ソフトウェア テクノロジ スイートの一部であり、私は AST の方法全体に同意しない限り、それらの使用方法を理解できませんでした。これは常に理解できるとは限りません。

あなたの例

明らかに存在する必要があります

extern char MasterSkipFile[];

どこかの .h ファイルで、適切な .o に対してリンクできます。

「プラットフォームに適した .h ファイルのセット」を条件付きで含めることは、可能な場合は ANSI C に固執し、不可能な場合はプラットフォーム固有の .h ファイルで互換性レイヤーを定義することで処理します。このままでは、s がインポートしようとしている名前がわから#includeないので、これ以上具体的なアドバイスをすることはできません。

于 2009-10-27T23:41:12.290 に答える