6

組み込みターゲットで C をプログラミングしています。複雑さとテスト容易性の問題が増大しているため、モジュール性は必須です。

一見すると、プログラムは制御ループです。内部ハードウェアを使用して物理入力を読み取り、いくつかの計算を適用し、計算された出力を適用します。ただし、コントロールは非常に洗練されており、多くの内部状態と変化する変数があります。

このコントロールはさまざまなモジュールに分割され、さまざまな状態の機能がまとめられています。DRY を維持するために、一般的なタスク/計算は個別のモジュールで提供され、別のモジュールで呼び出されます。プロジェクト全体で列挙型と型の一貫性を保つために、最上位の .H ファイルが使用されます (私の知る限り、継承などの OO 戦略は私のフレームワークではオプションではないため)。

モジュールとの間で変数を渡す方法を決定するときに、私の問題が発生します。

私の最初のアプローチは次のとおりです。

mymodule.H:

struct Inputs{
    int input1;
    ...
    int inputN;
}

struct Outputs{
    int output1;
    ...
    int outputN;
}

void mymodule(Inputs in,Outputs* out);

そして、メイン関数 (またはこのモジュールを呼び出すモジュール) で、「入力」および「出力」タイプの構造体を作成します。

次に、変数が Inputs 構造体にコピーされ、関数が呼び出され (Outputs 構造体を参照)、完了すると、この構造体の内容がさらなる計算に使用されます。

ただし、各モジュールは InputType および OutputType のインスタンスを呼び出し元のモジュールで作成する必要があるため、メモリ フットプリントが大きくなります。私の意見では、それはエレガントなソリューションではありません。ちなみに、私のプロジェクトでは動的割り当ては許可されていません。

良い解決策にたどり着くためのガイドラインや別のアイデアを教えていただけますか?

ありがとうございました。

追加した

解決策の 1 つは、InputStruct をポインターとして渡すこともできますが、それらは実質的にモジュールへの入力であるため、コードに沿って変更されていないことをどのように保証できますか?

追加した

ところで、発生するもう 1 つの問題は、すべてのモジュールが同じ変数を受け取るわけではなく、(これは C であるため) 継承メカニズムが利用できないため、各モジュールの構造に適切な値をロードする必要があるという事実です。私はかなり難読化されています...

4

3 に答える 3

5

関数の内外でパラメーターを渡すことで、大量のメモリ フットプリントを受け入れる必要はありません。重要なのは、パラメーターを参照渡しし、constキーワードを使用して入力が変更されないようにすることです。よく知られている例は次のとおりです。

int strcpy(char *destination, const char *source);

バッファのコピーではなく、ソースおよび宛先の文字バッファへのポインタのみが渡されますが、constキーワードは strcpy() がソース バッファの内容を変更するのを防ぎます。

非常に多くのパラメーターがあり、それぞれを個別に渡すことが現実的でない場合は、代わりに必ず構造体へのポインターを関数に渡します。constキーワードを使用して、入力が変更されないようにします。

int myFunc(struct myFuncOut *outputs, const struct myFuncIn *inputs);

構造体は参照によって渡されるため、myFunc() は呼び出し元の関数が使用するのと同じメモリで動作します (ただし、キーワードinputsのおかげで、が指すメモリに書き込むことはできません)。const関数へのポインタは、典型的な組み込みシステムでは 4 バイトであり、コピーのオーバーヘッドはありません。


ある関数からの出力を別の関数に入力として渡す必要があるという2番目の暗黙の問題については、パラメーターリストが同一ではないため、ある構造体から別の構造体にコピーする以外にできることはあまりないかもしれません。運が良ければ、次のようなことができるかもしれません。

struct myFunc1Out
{
    int common1;
    int common2;
    int common3;
};
struct myFunc2In
{
    int common1;
    int common2;
    int common3;
    int special1;
    int special2;
}

struct myFunc2In data;

myFunc1((struct myFunc1Out *)(*data), *inputs);
data.special1 = 1;
data.special2 = 2;
myFunc2(*outputs, *data);

ここで何が起こっているか分かりますか?struct myFunc2In の最初の部分は struct myFunc1Out と同じであるため、前者を後者にキャストするだけで、余分なフィールドは無視されます。これは、(非常に) 貧乏人のポリモーフィズムと考えることができます。

おそらくあまり目立たないもう 1 つの方法は、構造体 myFunc1Out を 2 番目の関数に、追加のパラメーター用の 2 番目の構造体と共に渡すことです。追加のメモリ コストは 1 つのポインターです。おそらく、データを論理グループに整理し、それぞれを構造体で表して、構造体が多すぎないようにする一方で、その構造体の残りの部分が使用される場合に必ずしも必要ではないフィールドを含む構造体がないようにすることはできますか?


ところで、あなたのコメントの 1 つは、構造体の定義が実行可能ファイルにメモリ オーバーヘッドを持っていることを期待していることを暗示しているように見えました。本当じゃない。メモリは、構造体のインスタンスが割り当てられている場合にのみ使用されます。

于 2013-04-11T07:44:20.043 に答える
1

module.c:

#include "module.h"

struct Inputs *getInput() { 
    static struct Inputs inputs;
    return &inputs;
}

struct Outputs *getOutput() { 
    static struct Outputs outputs;
    return &outputs;
}

struct Outputs *mymodule() {
    struct Outputs *o = getOutput();
    struct Inputs  *i = getInput();
    o->output[0] = i->input[0];
    return o;
}

module.h:

#define N 10

struct Inputs {
    int input[N];
};

struct Outputs {
    int output[N];
};


struct Inputs *getInputs();
struct Inputs *getOutputs();
void mymodule();
于 2013-04-10T12:00:42.060 に答える
0

1 つの可能性は、setter/getter 関数の背後に変数を隠し、プリプロセッサを使用してモジュール内のこれらの関数の可視性を制御することです。

ivars.c:

/* iovars.c */

static int s_input1;
static int s_input2;
static int s_output1;
static int s_output2;

int GetIn1(void)  { return s_input1; }
int GetIn2(void)  { return s_input2; }
void SetIn1(int value) { s_input1 = value; }
void SetIn2(int value) { s_input2 = value; }
int GetOut1(void)  { return s_output1; }
int GetOut2(void)  { return s_output2; }
void SetOut1(int value) { s_output1 = value; }
void SetOut2(int value) { s_ouput2 = value; }

ivars.h:

/* iovars.h */

#ifdef USING_IN1_READONLY || USING_IN1_READWRITE
int GetIn1(void);
#ifdef USING_IN1_READWRITE
void SetIn1(int value);
#endif
#endif

#ifdef USING_IN2_READONLY || USING_IN2_READWRITE
int GetIn2(void);
#ifdef USING_IN2_READWRITE
void SetIn2(int value);
#endif
#endif

#ifdef USING_OUT1_READONLY || USING_OUT1_READWRITE
int GetOut1(void);
#ifdef USING_OUT1_READWRITE
void SetOut1(int value);
#endif
#endif

#ifdef USING_OUT2_READONLY || USING_OUT2_READWRITE
int GetOut2(void);
#ifdef USING_OUT2_READWRITE
void SetOut2(int value);
#endif
#endif

少し面倒でしたが、モジュール内で変数ごとに可視性と書き込み可能性を制御できるようになりました。

/* moduleA.c */

#define USING_IN1_READONLY
#define USING_OUT1_READWRITE
#include "iovars.h"

/* code in this module can only see functions to read input 1
   and to read or write output 1 */

ところで、この回答は、これらすべての関数をインライン化することについての議論も含むこの質問への回答に基づいています。

于 2013-04-10T13:57:35.420 に答える