web-dev-qa-db-ja.com

埋め込みCコードの保守性のために#c .cソースファイルをインクルードしても大丈夫ですか?

私は専門のCプログラマーではありません。別のソースファイル.cを含めることは悪い習慣と見なされますが、保守性に役立つと思う状況があります。

私は多くの要素を持つ大きな構造を持ち、#defineを使用してインデックスを保持します。

#define TOTO_IND 0 
#define TITI_IND 1 
…
#define TATA_IND 50

static const MyElements elems [] = {
    {"TOTO", 18, "French"},
    {"TITI", 27, "English"},
    ...,
    {"TATA", 45, "Spanish"}
}

インデックスから構造にアクセスする必要があるため、#defineと構造宣言の同期を維持する必要があります。つまり、適切な場所に新しい要素を挿入し、それに応じて#defineを更新する必要があります。

エラーが発生しやすく、私はあまり好きではありません(ただし、パフォーマンスを考慮すると、より良い解決策は見つかりませんでした)。

とにかく、このファイルには、この構造を処理するための多くの関数も含まれています。また、コードの分離を維持し、グローバル変数を避けたいです。

物事を「簡単にする」ために、この「エラーを起こしやすい定義」を、この構造のみを含む単一の.cソースファイルに移動することを考えていました。このファイルは「危険で注意が必要なファイル」であり、実際の「通常の機能」ファイルに含めます。

あなたはそれについてどう思いますか? .cソースファイルを含めるのに有効な状況ですか?私の構造を処理する別のより良い方法はありますか?

29
ncenerar

Ian Abbotの回答に示されているように、指定された初期化子を使用する必要があります。

さらに、配列インデックスがここにあるように隣接している場合、代わりに列挙を使用できます:

toto.h

typedef enum
{
  TOTO_IND,
  TITI_IND,
  ...
  TATA_IND,
  TOTO_N    // this is not a data item but the number of items in the enum
} toto_t;

toto.c

const MyElements elems [] = {
  [TITI_IND] = {"TITI", 27, "English"},
  [TATA_IND] = {"TATA", 45, "Spanish"},
  [TOTO_IND] = {"TOTO", 18, "French"},
};

そして今、あなたは静的アサートで配列全体のデータの整合性を検証することができます:

_Static_assert(sizeof elems/sizeof *elems == TOTO_N, 
               "Mismatch between toto_t and elems is causing rain in Africa");
_Static_assert(sizeof elems/sizeof *elems == TOTO_N, ERR_MSG);

ここで、ERR_MSGは次のように定義されます

#define STR(x) STR2(x)
#define STR2(x) #x
#define ERR_MSG "Mismatching toto_t. Holding on line " STR(__LINE__)
33
Lundin

指定された初期化子を使用して、各インデックス識別子(またはマクロ)の明示的な値を知らなくても、elems[]の要素を初期化できます。

const MyElements elems[] = {
    [TOTO_IND] = {"TOTO", 18, "French"},
    [TITI_IND] = {"TITI", 27, "English"},
    [TATA_IND] = {"TATA", 45, "Spanish"},
};

ソースコードに表示される順序を変更しても、配列要素は同じ方法で初期化されます。

const MyElements elems[] = {
    [TITI_IND] = {"TITI", 27, "English"},
    [TATA_IND] = {"TATA", 45, "Spanish"},
    [TOTO_IND] = {"TOTO", 18, "French"},
};

配列の長さが上記のようにイニシャライザから自動的に設定された場合(つまり、[]ではなく[NUM_ELEMS]を使用して)、長さは最大要素インデックスより1つ大きくなります。

これにより、elems配列のインデックス値と外部宣言を.hファイルに保持し、elems配列の内容を別の.cファイルに定義できます。

39
Ian Abbott

他の答えはすでにより明確な方法でそれをカバーしていますが、完全を期すために、このルートを進んで同僚の怒りを危険にさらすことを望んでいるなら、ここにXマクロのアプローチがあります。

Xマクロは、組み込みCプリプロセッサを使用したコード生成の形式です。目標は、繰り返しを最小限に抑えることですが、いくつかの欠点があります。

  1. プリプロセッサを使用して列挙型と構造体を生成するソースファイルは、慣れていない場合は複雑に見えるかもしれません。
  2. ソースファイルを生成する外部ビルドスクリプトと比較すると、xマクロでは、コンパイラ設定を使用して前処理されたファイルを手動で確認しない限り、生成されたコードがコンパイル中にどのように見えるかがわかりません。
  3. 前処理された出力が表示されないため、デバッガーを使用して、外部スクリプトによって生成されたコードと同じ方法で生成されたコードをステップ実行することはできません。

まず、マクロinvocationsのリストを別のファイルに作成することから始めます。 elements.inc、この時点でマクロが実際に行うことを定義せずに:

// elements.inc

// each row passes a set of parameters to the macro,
// although at this point we haven't defined what the
// macro will output

XMACRO(TOTO, 18, French)
XMACRO(TITI, 27, English)
XMACRO(TATA, 45, Spanish)

そして、このリストを含める必要があるたびにマクロを定義して、各呼び出しが作成するコンストラクトの単一行にレンダリングされるようにします。通常、これを連続して数回繰り返します。

// concatenate id with "_IND" to create enums, ignore code and description
// (notice how you don't need to use all parameters each time)
// e.g. XMACRO(TOTO, 18, French) => TOTO_IND,
#define XMACRO(id, code, description) id ## _IND,
typedef enum
{
#    include "elements.inc"
     ELEMENTS_COUNT
}
Elements;
#undef XMACRO

// create struct entries
// e.g. XMACRO(TOTO, 18, French) => [TOTO_IND] = { "TOTO", 18, "French" },
#define XMACRO(id, code, description) [id ## _IND] = { #id, code, #description },
const MyElements elems[] = {
{
#    include "elements.inc"
};
#undef XMACRO

次のように前処理されます:

typedef enum
{
    TOTO_IND,
    TITI_IND,
    TATA_IND,
    ELEMENTS_COUNT
}
Elements;

const MyElements elems[] = {
{
    [TOTO_IND] = { "TOTO", 18, "French" },
    [TITI_IND] = { "TITI", 27, "English" },
    [TATA_IND] = { "TATA", 45, "Spanish" },
};

明らかに、リストの頻繁なメンテナンスはより簡単になりますが、コードの生成は複雑になります。

18
Groo

constを複数のファイルでstaticとして定義することは、大きな変数MyElementsの複数のインスタンスを作成するため、お勧めできません。これにより、組み込みシステムのメモリが増加します。 static修飾子を削除する必要があります。

推奨される解決策は次のとおりです。

file.hで

#define TOTO_IND 0 
#define TITI_IND 1 
…
#define TATA_IND 50
#define MAX_ELEMS 51

extern const MyElements elems[MAX_ELEMS];

file.cで

#include "file.h"
const MyElements elems [MAX_ELEMS] = {
    {"TOTO", 18, "French"},
    {"TITI", 27, "English"},
    ...,
    {"TATA", 45, "Spanish"}
}

変更後、#include "file.h"を必要な.cファイルに配置します。

5
Rishikesh Raje

#include.cファイルとともに使用することに関する特定の質問に対処するために(他の回答は、より良いオプション、特にGrooによるオプションを示唆しています)、通常は必要ありません。

.cファイル内のすべてを外部から表示およびアクセス可能にすることができるため、関数プロトタイプおよび#externを介して参照できます。たとえば、メインの#extern const MyElements elems [];ファイルで.cを使用してテーブルを参照できます。

または、定義を.hファイルに入れて含めることもできます。これにより、必要に応じてコードを分離できます。 #includeは、含まれるファイルの内容を#includeステートメントがある場所に挿入するだけなので、特定のファイル拡張子を持つ必要はないことに注意してください。 .hは慣例により使用され、ほとんどのIDEは.cファイルをコンパイルされるファイルのリストに自動的に追加しますが、コンパイラーに関する限り、命名は任意です。

1
shogged